diff --git a/lib/common/appstart/localization_initialize.dart b/lib/common/appstart/localization_initialize.dart index c773fd90..37d772b1 100644 --- a/lib/common/appstart/localization_initialize.dart +++ b/lib/common/appstart/localization_initialize.dart @@ -9,8 +9,8 @@ import 'package:kiwi/kiwi.dart'; /// correct language /// class LocalizationInitialize { - PreferencesProvider _preferencesProvider; - String _languageCode; + PreferencesProvider? _preferencesProvider; + String? _languageCode; /// /// Initialize the localization using the provided language code diff --git a/lib/common/data/database_access.dart b/lib/common/data/database_access.dart index 8dfd9819..6b8a3851 100644 --- a/lib/common/data/database_access.dart +++ b/lib/common/data/database_access.dart @@ -4,15 +4,12 @@ import 'package:sqflite/sqflite.dart'; class DatabaseAccess { static const String _databaseName = "Database.db"; - static Database _databaseInstance; + static Database? _databaseInstance; static const String idColumnName = "id"; Future get _database async { - if (_databaseInstance != null) return _databaseInstance; - - _databaseInstance = await _initDatabase(); - return _databaseInstance; + return _databaseInstance ??= await _initDatabase(); } Future _initDatabase() async { @@ -60,19 +57,20 @@ class DatabaseAccess { } Future>> queryRows(String table, - {bool distinct, - List columns, - String where, - List whereArgs, - String groupBy, - String having, - String orderBy, - int limit, - int offset}) async { + {bool? distinct, + List? columns, + String? where, + List? whereArgs, + String? groupBy, + String? having, + String? orderBy, + int? limit, + int? offset}) async { Database db = await _database; + // TODO: [Leptopoda] is there a reason this is done? or at maybe use whereArgs.removeWhere() for (int i = 0; i < (whereArgs?.length ?? 0); i++) { - whereArgs[i] = whereArgs[i] ?? ""; + whereArgs![i] = whereArgs[i] ?? ""; } return await db.query( @@ -89,33 +87,33 @@ class DatabaseAccess { ); } - Future queryRowCount(String table) async { + Future queryRowCount(String table) async { Database db = await _database; return Sqflite.firstIntValue( await db.rawQuery('SELECT COUNT(*) FROM $table')); } - Future queryAggregator(String query, List arguments) async { + Future queryAggregator(String query, List arguments) async { Database db = await _database; return Sqflite.firstIntValue(await db.rawQuery(query, arguments)); } Future update(String table, Map row) async { Database db = await _database; - int id = row[idColumnName]; + int? id = row[idColumnName]; return await db .update(table, row, where: '$idColumnName = ?', whereArgs: [id]); } - Future delete(String table, int id) async { + Future delete(String table, int? id) async { Database db = await _database; return await db.delete(table, where: '$idColumnName = ?', whereArgs: [id]); } Future deleteWhere( String table, { - String where, - List whereArgs, + String? where, + List? whereArgs, }) async { Database db = await _database; return await db.delete( diff --git a/lib/common/data/database_entity.dart b/lib/common/data/database_entity.dart index 121892b8..65967e9e 100644 --- a/lib/common/data/database_entity.dart +++ b/lib/common/data/database_entity.dart @@ -1,4 +1,4 @@ abstract class DatabaseEntity { Map toMap(); - void fromMap(Map map); + fromMap(Map map); } diff --git a/lib/common/data/database_path_provider.dart b/lib/common/data/database_path_provider.dart index 6d78badc..e5a5be00 100644 --- a/lib/common/data/database_path_provider.dart +++ b/lib/common/data/database_path_provider.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart'; -import 'package:ios_app_group/ios_app_group.dart'; +import 'package:app_group_directory/app_group_directory.dart'; Future getDatabasePath(String databaseName) async { Directory documentsDirectory = await getApplicationDocumentsDirectory(); @@ -24,12 +24,13 @@ Future _getiOSDatabasePathAndMigrate( // it must be saved in a group shared between the app module and the widget // module. "Migration" means that the database at the old path gets // copied to the new path + assert(Platform.isIOS); - var groupDirectory = await IosAppGroup.getAppGroupDirectory( + Directory? groupDirectory = await AppGroupDirectory.getAppGroupDirectory( 'group.de.bennik2000.dhbwstudentapp', ); - var newPath = join(groupDirectory.path, databaseName); + var newPath = join(groupDirectory!.path, databaseName); var migrateSuccess = await _migrateOldDatabase(oldPath, newPath); diff --git a/lib/common/data/preferences/app_theme_enum.dart b/lib/common/data/preferences/app_theme_enum.dart index 01aab80e..0c8be05f 100644 --- a/lib/common/data/preferences/app_theme_enum.dart +++ b/lib/common/data/preferences/app_theme_enum.dart @@ -1,9 +1,4 @@ - /// /// Enum which holds the possible themes the app can be displayed in /// -enum AppTheme { - Dark, - Light, - System -} \ No newline at end of file +enum AppTheme { Dark, Light, System } diff --git a/lib/common/data/preferences/preferences_access.dart b/lib/common/data/preferences/preferences_access.dart index 20cc4c9c..ea183eae 100644 --- a/lib/common/data/preferences/preferences_access.dart +++ b/lib/common/data/preferences/preferences_access.dart @@ -17,32 +17,26 @@ class PreferencesAccess { case int: await prefs.setInt(key, value as int); return; + default: + throw InvalidValueTypeException(T); } - - throw InvalidValueTypeException(T); } - Future get(String key) async { + Future get(String key) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - T value; - switch (T) { case bool: - value = prefs.getBool(key) as T; - break; + return prefs.getBool(key) as T?; case String: - value = prefs.getString(key) as T; - break; + return prefs.getString(key) as T?; case double: - value = prefs.getDouble(key) as T; - break; + return prefs.getDouble(key) as T?; case int: - value = prefs.getInt(key) as T; - break; + return prefs.getInt(key) as T?; + default: + return null; } - - return value; } } diff --git a/lib/common/data/preferences/preferences_provider.dart b/lib/common/data/preferences/preferences_provider.dart index 5c6beaf5..61533ddd 100644 --- a/lib/common/data/preferences/preferences_provider.dart +++ b/lib/common/data/preferences/preferences_provider.dart @@ -51,23 +51,23 @@ class PreferencesProvider { } Future setIsCalendarSyncEnabled(bool value) async { - await _preferencesAccess.set('isCalendarSyncEnabled', value); + await _preferencesAccess.set('isCalendarSyncEnabled', value); } Future isCalendarSyncEnabled() async { - return await _preferencesAccess.get('isCalendarSyncEnabled') ?? false; + return await _preferencesAccess.get('isCalendarSyncEnabled') ?? false; } - Future setSelectedCalendar(Calendar selectedCalendar) async { - String selectedCalendarId = selectedCalendar?.id; - await _preferencesAccess.set( - 'SelectedCalendarId', selectedCalendarId ?? ''); + Future setSelectedCalendar(Calendar? selectedCalendar) async { + final selectedCalendarId = selectedCalendar?.id ?? ""; + await _preferencesAccess.set( + 'SelectedCalendarId', selectedCalendarId); } - Future getSelectedCalendar() async { - Calendar selectedCalendar; - String selectedCalendarId = - await _preferencesAccess.get('SelectedCalendarId') ?? null; + Future getSelectedCalendar() async { + Calendar? selectedCalendar; + String? selectedCalendarId = + await _preferencesAccess.get('SelectedCalendarId'); if (selectedCalendarId == null) return null; List availableCalendars = await CalendarAccess().queryWriteableCalendars(); @@ -78,27 +78,27 @@ class PreferencesProvider { } Future getRaplaUrl() async { - return await _preferencesAccess.get(RaplaUrlKey) ?? ""; + return await _preferencesAccess.get(RaplaUrlKey) ?? ""; } Future setRaplaUrl(String url) async { - await _preferencesAccess.set(RaplaUrlKey, url); + await _preferencesAccess.set(RaplaUrlKey, url); } Future isFirstStart() async { - return await _preferencesAccess.get(IsFirstStartKey) ?? true; + return await _preferencesAccess.get(IsFirstStartKey) ?? true; } Future setIsFirstStart(bool isFirstStart) async { - await _preferencesAccess.set(IsFirstStartKey, isFirstStart); + await _preferencesAccess.set(IsFirstStartKey, isFirstStart); } - Future getLastUsedLanguageCode() async { + Future getLastUsedLanguageCode() async { return await _preferencesAccess.get(LastUsedLanguageCode); } Future setLastUsedLanguageCode(String languageCode) async { - await _preferencesAccess.set(LastUsedLanguageCode, languageCode); + await _preferencesAccess.set(LastUsedLanguageCode, languageCode); } Future getNotifyAboutNextDay() async { @@ -106,7 +106,7 @@ class PreferencesProvider { } Future setNotifyAboutNextDay(bool value) async { - await _preferencesAccess.set(NotifyAboutNextDay, value); + await _preferencesAccess.set(NotifyAboutNextDay, value); } Future getNotifyAboutScheduleChanges() async { @@ -115,7 +115,7 @@ class PreferencesProvider { } Future setNotifyAboutScheduleChanges(bool value) async { - await _preferencesAccess.set(NotifyAboutScheduleChanges, value); + await _preferencesAccess.set(NotifyAboutScheduleChanges, value); } Future getDontShowRateNowDialog() async { @@ -123,17 +123,22 @@ class PreferencesProvider { } Future setDontShowRateNowDialog(bool value) async { - await _preferencesAccess.set(DontShowRateNowDialog, value); + await _preferencesAccess.set(DontShowRateNowDialog, value); } Future storeDualisCredentials(Credentials credentials) async { - await _secureStorageAccess.set(DualisUsername, credentials.username ?? ""); - await _secureStorageAccess.set(DualisPassword, credentials.password ?? ""); + await _secureStorageAccess.set(DualisUsername, credentials.username); + await _secureStorageAccess.set(DualisPassword, credentials.password); } - Future loadDualisCredentials() async { + Future loadDualisCredentials() async { var username = await _secureStorageAccess.get(DualisUsername); var password = await _secureStorageAccess.get(DualisPassword); + + if (username == null || + password == null || + username.isEmpty || + password.isEmpty) return null; return Credentials(username, password); } @@ -147,30 +152,34 @@ class PreferencesProvider { } Future setStoreDualisCredentials(bool value) async { - await _preferencesAccess.set(DualisStoreCredentials, value ?? false); + await _preferencesAccess.set(DualisStoreCredentials, value); } - Future getLastViewedSemester() async { + Future getLastViewedSemester() async { return await _preferencesAccess.get(LastViewedSemester); } - Future setLastViewedSemester(String lastViewedSemester) async { - await _preferencesAccess.set(LastViewedSemester, lastViewedSemester); + Future setLastViewedSemester(String? lastViewedSemester) async { + if (lastViewedSemester == null) return; + await _preferencesAccess.set( + LastViewedSemester, lastViewedSemester); } - Future getLastViewedDateEntryDatabase() async { + Future getLastViewedDateEntryDatabase() async { return await _preferencesAccess.get(LastViewedDateEntryDatabase); } - Future setLastViewedDateEntryDatabase(String value) async { - await _preferencesAccess.set(LastViewedDateEntryDatabase, value); + Future setLastViewedDateEntryDatabase(String? value) async { + await _preferencesAccess.set( + LastViewedDateEntryDatabase, value ?? ""); } - Future getLastViewedDateEntryYear() async { + Future getLastViewedDateEntryYear() async { return await _preferencesAccess.get(LastViewedDateEntryYear); } - Future setLastViewedDateEntryYear(String value) async { + Future setLastViewedDateEntryYear(String? value) async { + if (value == null) return; await _preferencesAccess.set(LastViewedDateEntryYear, value); } @@ -182,85 +191,88 @@ class PreferencesProvider { await _preferencesAccess.set(ScheduleSourceType, value); } - Future getIcalUrl() { - return _preferencesAccess.get(ScheduleIcalUrl); + Future getIcalUrl() { + return _preferencesAccess.get(ScheduleIcalUrl); } Future setIcalUrl(String url) { - return _preferencesAccess.set(ScheduleIcalUrl, url); + return _preferencesAccess.set(ScheduleIcalUrl, url); } - Future getMannheimScheduleId() { - return _preferencesAccess.get(MannheimScheduleId); + Future getMannheimScheduleId() { + return _preferencesAccess.get(MannheimScheduleId); } Future setMannheimScheduleId(String url) { - return _preferencesAccess.set(MannheimScheduleId, url); + return _preferencesAccess.set(MannheimScheduleId, url); } Future getPrettifySchedule() async { - return await _preferencesAccess.get(PrettifySchedule) ?? true; + return await _preferencesAccess.get(PrettifySchedule) ?? true; } Future setPrettifySchedule(bool value) { - return _preferencesAccess.set(PrettifySchedule, value); + return _preferencesAccess.set(PrettifySchedule, value); } Future getSynchronizeScheduleWithCalendar() async { - return await _preferencesAccess.get(SynchronizeScheduleWithCalendar) ?? + return await _preferencesAccess + .get(SynchronizeScheduleWithCalendar) ?? true; } Future setSynchronizeScheduleWithCalendar(bool value) { - return _preferencesAccess.set(SynchronizeScheduleWithCalendar, value); + return _preferencesAccess.set(SynchronizeScheduleWithCalendar, value); } Future getDidShowWidgetHelpDialog() async { - return await _preferencesAccess.get(DidShowWidgetHelpDialog) ?? false; + return await _preferencesAccess.get(DidShowWidgetHelpDialog) ?? false; } Future setDidShowWidgetHelpDialog(bool value) { - return _preferencesAccess.set(DidShowWidgetHelpDialog, value); + return _preferencesAccess.set(DidShowWidgetHelpDialog, value); } Future set(String key, T value) async { + if (value == null) return; return _preferencesAccess.set(key, value); } - Future get(String key) async { - return _preferencesAccess.get(key); + Future get(String key) async { + return _preferencesAccess.get(key); } Future getAppLaunchCounter() async { - return await _preferencesAccess.get("AppLaunchCount") ?? 0; + return await _preferencesAccess.get("AppLaunchCount") ?? 0; } Future setAppLaunchCounter(int value) async { - return await _preferencesAccess.set("AppLaunchCount", value); + return await _preferencesAccess.set("AppLaunchCount", value); } Future getNextRateInStoreLaunchCount() async { - return await _preferencesAccess.get("NextRateInStoreLaunchCount") ?? + return await _preferencesAccess.get("NextRateInStoreLaunchCount") ?? RateInStoreLaunchAfter; } Future setNextRateInStoreLaunchCount(int value) async { - return await _preferencesAccess.set("NextRateInStoreLaunchCount", value); + return await _preferencesAccess.set( + "NextRateInStoreLaunchCount", value); } Future getDidShowDonateDialog() async { - return await _preferencesAccess.get("DidShowDonateDialog") ?? false; + return await _preferencesAccess.get("DidShowDonateDialog") ?? false; } Future setDidShowDonateDialog(bool value) { - return _preferencesAccess.set("DidShowDonateDialog", value); + return _preferencesAccess.set("DidShowDonateDialog", value); } Future getHasPurchasedSomething() async { - return await _preferencesAccess.get("HasPurchasedSomething") ?? false; + return await _preferencesAccess.get("HasPurchasedSomething") ?? false; } Future setHasPurchasedSomething(bool value) { - return _preferencesAccess.set("HasPurchasedSomething", value); + return _preferencesAccess.set("HasPurchasedSomething", value); } } diff --git a/lib/common/data/preferences/secure_storage_access.dart b/lib/common/data/preferences/secure_storage_access.dart index ec8e7442..76745504 100644 --- a/lib/common/data/preferences/secure_storage_access.dart +++ b/lib/common/data/preferences/secure_storage_access.dart @@ -7,7 +7,7 @@ class SecureStorageAccess { await _secureStorage.write(key: key, value: value); } - Future get(String key) async { + Future get(String key) async { return await _secureStorage.read(key: key); } } diff --git a/lib/common/i18n/localization_strings_de.dart b/lib/common/i18n/localization_strings_de.dart index 252116e8..753959be 100644 --- a/lib/common/i18n/localization_strings_de.dart +++ b/lib/common/i18n/localization_strings_de.dart @@ -5,7 +5,8 @@ final de = { "settingsViewSourceCode": "Source code auf GitHub ansehen", "settingsCalendarSync": "Kalendersynchronisation", "calendarSyncPageTitle": "Kalender synchronisieren", - "calendarSyncPageSubtitle": "Wähle den Kalender aus, in den der Vorlesungsplan übertragen werden soll:", + "calendarSyncPageSubtitle": + "Wähle den Kalender aus, in den der Vorlesungsplan übertragen werden soll:", "calendarSyncPageEndSync": "Synchronisation beenden", "calendarSyncPageBeginSync": "Kalender synchronisieren", "applicationName": "DHBW Studenten App", @@ -114,7 +115,8 @@ final de = { "dialogOk": "Ok", "dialogCancel": "Cancel", "dialogSetRaplaUrlTitle": "Rapla Url festlegen", - "dialogCalendarAccessNotGranted": "Wenn du den Vorlesungsplan mit deinem nativen Kalender synchronisieren möchtest, musst du in den Einstellungen den Kalenderzugriff erlauben.", + "dialogCalendarAccessNotGranted": + "Wenn du den Vorlesungsplan mit deinem nativen Kalender synchronisieren möchtest, musst du in den Einstellungen den Kalenderzugriff erlauben.", "dialogTitleCalendarAccessNotGranted": "Keine Zugriffsrechte", "scheduleEmptyStateSetUrl": "Konfigurieren", "scheduleEmptyStateBannerMessage": diff --git a/lib/common/i18n/localizations.dart b/lib/common/i18n/localizations.dart index c43f5842..fb82fdbc 100644 --- a/lib/common/i18n/localizations.dart +++ b/lib/common/i18n/localizations.dart @@ -6,10 +6,10 @@ import 'package:flutter/widgets.dart'; class L { final Locale locale; - String _language; + late String _language; L(this.locale) { - _language = locale?.languageCode?.substring(0, 2); + _language = locale.languageCode.substring(0, 2); if (!_localizedValues.containsKey(_language)) { _language = "en"; @@ -84,7 +84,8 @@ class L { String get calendarSyncPageEndSync => _getValue("calendarSyncPageEndSync"); - String get calendarSyncPageBeginSync => _getValue("calendarSyncPageBeginSync"); + String get calendarSyncPageBeginSync => + _getValue("calendarSyncPageBeginSync"); String get notificationScheduleChangedNewClass => _getValue("notificationScheduleChangedNewClass"); @@ -283,9 +284,11 @@ class L { String get dialogSetRaplaUrlTitle => _getValue("dialogSetRaplaUrlTitle"); - String get dialogCalendarAccessNotGranted => _getValue("dialogCalendarAccessNotGranted"); + String get dialogCalendarAccessNotGranted => + _getValue("dialogCalendarAccessNotGranted"); - String get dialogTitleCalendarAccessNotGranted => _getValue("dialogTitleCalendarAccessNotGranted"); + String get dialogTitleCalendarAccessNotGranted => + _getValue("dialogTitleCalendarAccessNotGranted"); String get scheduleEmptyStateSetUrl => _getValue("scheduleEmptyStateSetUrl"); @@ -403,7 +406,7 @@ class L { String get filterTitle => _getValue("filterTitle"); static L of(BuildContext context) { - return Localizations.of(context, L); + return Localizations.of(context, L)!; } static final Map> _localizedValues = { @@ -412,7 +415,7 @@ class L { }; String _getValue(String key) { - return _localizedValues[_language][key] ?? ""; + return _localizedValues[_language]![key] ?? ""; } String getValue(String key) => _getValue(key); diff --git a/lib/common/iap/in_app_purchase_helper.dart b/lib/common/iap/in_app_purchase_helper.dart index efe60867..3d9d50cd 100644 --- a/lib/common/iap/in_app_purchase_helper.dart +++ b/lib/common/iap/in_app_purchase_helper.dart @@ -20,7 +20,7 @@ enum PurchaseResultEnum { } typedef PurchaseCompletedCallback = Function( - String productId, + String? productId, PurchaseResultEnum result, ); @@ -33,10 +33,10 @@ class InAppPurchaseHelper { final PreferencesProvider _preferencesProvider; - StreamSubscription _purchaseUpdatedSubscription; - StreamSubscription _purchaseErrorSubscription; + StreamSubscription? _purchaseUpdatedSubscription; + StreamSubscription? _purchaseErrorSubscription; - PurchaseCompletedCallback _purchaseCallback; + PurchaseCompletedCallback? _purchaseCallback; InAppPurchaseHelper(this._preferencesProvider); @@ -77,7 +77,7 @@ class InAppPurchaseHelper { return PurchaseResultEnum.Success; } on PlatformException catch (_) { if (_purchaseCallback != null) { - _purchaseCallback(id, PurchaseResultEnum.Error); + _purchaseCallback!(id, PurchaseResultEnum.Error); } return PurchaseResultEnum.Error; @@ -95,25 +95,23 @@ class InAppPurchaseHelper { return PurchaseStateEnum.NotPurchased; } - var allPurchases = []; - try { - allPurchases = + var allPurchases = await FlutterInappPurchase.instance.getAvailablePurchases(); - } on Exception catch (_) { - return PurchaseStateEnum.Unknown; - } - var productIdPurchases = - allPurchases.where((element) => element.productId == id); + var productIdPurchases = + allPurchases?.where((element) => element.productId == id); + + if (productIdPurchases?.isNotEmpty ?? false) { + return productIdPurchases!.any((element) => _isPurchased(element)) + ? PurchaseStateEnum.Purchased + : PurchaseStateEnum.NotPurchased; + } - if (productIdPurchases.isEmpty) { return PurchaseStateEnum.NotPurchased; + } on Exception catch (_) { + return PurchaseStateEnum.Unknown; } - - return productIdPurchases.any((element) => _isPurchased(element)) - ? PurchaseStateEnum.Purchased - : PurchaseStateEnum.NotPurchased; } /// @@ -124,7 +122,9 @@ class InAppPurchaseHelper { _purchaseCallback = callback; } - Future _completePurchase(PurchasedItem item) async { + Future _completePurchase(PurchasedItem? item) async { + if (item == null) return; + var purchaseResult = _purchaseResultFromItem(item); _purchaseCallback?.call(item.productId, purchaseResult); @@ -152,48 +152,40 @@ class InAppPurchaseHelper { } PurchaseResultEnum _purchaseResultFromItem(PurchasedItem item) { - var purchaseResult = PurchaseResultEnum.Error; - if (Platform.isAndroid) { switch (item.purchaseStateAndroid) { case PurchaseState.pending: - purchaseResult = PurchaseResultEnum.Pending; - break; + return PurchaseResultEnum.Pending; + case PurchaseState.purchased: - purchaseResult = PurchaseResultEnum.Success; - break; + return PurchaseResultEnum.Success; case PurchaseState.unspecified: - purchaseResult = PurchaseResultEnum.Error; - break; + default: + return PurchaseResultEnum.Error; } } else if (Platform.isIOS) { switch (item.transactionStateIOS) { case TransactionState.purchasing: - purchaseResult = PurchaseResultEnum.Pending; - break; + return PurchaseResultEnum.Pending; case TransactionState.purchased: - purchaseResult = PurchaseResultEnum.Success; - break; - case TransactionState.failed: - purchaseResult = PurchaseResultEnum.Error; - break; + return PurchaseResultEnum.Success; case TransactionState.restored: - purchaseResult = PurchaseResultEnum.Success; - break; + return PurchaseResultEnum.Success; case TransactionState.deferred: - purchaseResult = PurchaseResultEnum.Pending; - break; + return PurchaseResultEnum.Pending; + case TransactionState.failed: + default: + return PurchaseResultEnum.Error; } } - - return purchaseResult; + return PurchaseResultEnum.Error; } Future _hasFinishedTransaction(PurchasedItem item) async { if (item.isAcknowledgedAndroid ?? false) return true; return await _preferencesProvider - .get("purchase_${item.productId}_finished") ?? + .get("purchase_${item.productId}_finished") ?? false; } @@ -206,7 +198,7 @@ class InAppPurchaseHelper { return; } - List purchasedItems = []; + List? purchasedItems; if (Platform.isAndroid) { purchasedItems = @@ -216,20 +208,23 @@ class InAppPurchaseHelper { await FlutterInappPurchase.instance.getPendingTransactionsIOS(); } - print("Found ${purchasedItems.length} pending purchases"); + if (purchasedItems != null) { + print("Found ${purchasedItems.length} pending purchases"); - purchasedItems.forEach(_completePurchase); + purchasedItems.forEach(_completePurchase); + } } - void _onPurchaseError(PurchaseResult event) { + void _onPurchaseError(PurchaseResult? event) { print("Failed to purchase:"); - print(event.message); - print(event.debugMessage); + print(event?.message); + print(event?.debugMessage); _purchaseCallback?.call(null, PurchaseResultEnum.Error); } - bool _isConsumable(String id) { + // TODO: [Leptopdoa] remove this¿? + bool _isConsumable(String? id) { return false; } diff --git a/lib/common/iap/in_app_purchase_manager.dart b/lib/common/iap/in_app_purchase_manager.dart index c56b8f96..75b7d540 100644 --- a/lib/common/iap/in_app_purchase_manager.dart +++ b/lib/common/iap/in_app_purchase_manager.dart @@ -22,7 +22,7 @@ class InAppPurchaseManager { void _initialize() async { addPurchaseCallback( InAppPurchaseHelper.WidgetProductId, - (String productId, PurchaseResultEnum result) => + (String? productId, PurchaseResultEnum result) => _setWidgetEnabled(result == PurchaseResultEnum.Success), ); @@ -32,8 +32,8 @@ class InAppPurchaseManager { try { await _inAppPurchaseHelper.initialize(); await _restorePurchases(); - } - catch (ex) { + } catch (ex) { + // TODO: [Leptopoda] disable purchases or show error message in settings when initialization was not sucessfull (i.e. no play services) print("Failed to initialize in app purchase!"); } } @@ -61,19 +61,21 @@ class InAppPurchaseManager { await buyWidget(); } - void _purchaseCompletedCallback(String productId, PurchaseResultEnum result) { + // TODO: [Leptopoda] better nullseafety + void _purchaseCompletedCallback( + String? productId, PurchaseResultEnum result) { if (purchaseCallbacks.containsKey(productId)) { - var callback = purchaseCallbacks[productId] ?? []; + var callback = purchaseCallbacks[productId!]; - callback.forEach((element) { + callback?.forEach((element) { element(productId, result); }); } if (purchaseCallbacks.containsKey("*")) { - var callback = purchaseCallbacks["*"] ?? []; + var callback = purchaseCallbacks["*"]; - callback.forEach((element) { + callback?.forEach((element) { element(productId, result); }); } @@ -91,25 +93,21 @@ class InAppPurchaseManager { /// for all product ids, pass null or "*" as productId /// void addPurchaseCallback( - String productId, + String? productId, PurchaseCompletedCallback callback, ) { if (productId == null) { productId = "*"; } - if (!purchaseCallbacks.containsKey(productId)) { - purchaseCallbacks[productId] = []; - } - - purchaseCallbacks[productId].add(callback); + purchaseCallbacks[productId]?.add(callback); } /// /// Removes a callback which was registered using [addPurchaseCallback] /// void removePurchaseCallback( - String productId, + String? productId, PurchaseCompletedCallback callback, ) { if (productId == null) { diff --git a/lib/common/logging/crash_reporting.dart b/lib/common/logging/crash_reporting.dart index ad0dde6b..0e863cfa 100644 --- a/lib/common/logging/crash_reporting.dart +++ b/lib/common/logging/crash_reporting.dart @@ -1,7 +1,7 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; -Future reportException(ex, StackTrace trace) async { +Future reportException(ex, StackTrace? trace) async { if (kReleaseMode) { print("Reporting exception to crashlytics: $ex with stack trace $trace"); await FirebaseCrashlytics.instance.recordError(ex, trace); diff --git a/lib/common/ui/colors.dart b/lib/common/ui/colors.dart index 181b7c65..1cea4e06 100644 --- a/lib/common/ui/colors.dart +++ b/lib/common/ui/colors.dart @@ -68,7 +68,7 @@ Color colorNoConnectionForeground() => Colors.white; class ColorPalettes { ColorPalettes._(); - static ThemeData buildTheme(AppTheme theme) { + static ThemeData buildTheme(AppTheme? theme) { if (theme == AppTheme.System) { theme = PlatformUtil.platformBrightness() == Brightness.light ? AppTheme.Light @@ -93,14 +93,14 @@ class ColorPalettes { return themeData.copyWith( snackBarTheme: themeData.snackBarTheme.copyWith( backgroundColor: isDark ? Color(0xff363635) : Color(0xfffafafa), - contentTextStyle: themeData.textTheme.bodyText1.copyWith( + contentTextStyle: themeData.textTheme.bodyText1!.copyWith( color: - isDark ? Color(0xffe4e4e4) : themeData.textTheme.bodyText1.color, + isDark ? Color(0xffe4e4e4) : themeData.textTheme.bodyText1!.color, ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( - primary: ColorPalettes.main, + foregroundColor: ColorPalettes.main, padding: EdgeInsets.symmetric(horizontal: 16.0), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(4.0)), diff --git a/lib/common/ui/custom_icons_icons.dart b/lib/common/ui/custom_icons_icons.dart index 759832fb..39da89d1 100644 --- a/lib/common/ui/custom_icons_icons.dart +++ b/lib/common/ui/custom_icons_icons.dart @@ -11,7 +11,7 @@ /// fonts: /// - asset: fonts/CustomIcons.ttf /// -/// +/// /// * Font Awesome 4, Copyright (C) 2016 by Dave Gandy /// Author: Dave Gandy /// License: SIL () @@ -23,7 +23,8 @@ class CustomIcons { CustomIcons._(); static const _kFontFam = 'CustomIcons'; - static const _kFontPkg = null; + static const dynamic _kFontPkg = null; - static const IconData logout = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData logout = + IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); } diff --git a/lib/common/ui/notification_api.dart b/lib/common/ui/notification_api.dart index 9810bf1e..ae714d84 100644 --- a/lib/common/ui/notification_api.dart +++ b/lib/common/ui/notification_api.dart @@ -20,6 +20,7 @@ class NotificationApi { ); var initializationSettingsIOS = IOSInitializationSettings( + // TODO: [Leptopoda] the below always returns null so why register it? onDidReceiveLocalNotification: onDidReceiveLocalNotification, ); @@ -37,7 +38,7 @@ class NotificationApi { /// /// Show a notification with the given title and message /// - Future showNotification(String title, String message, [int id]) async { + Future showNotification(String title, String? message, [int? id]) async { var androidPlatformChannelSpecifics = AndroidNotificationDetails( 'Notifications', 'Notifications', @@ -72,10 +73,10 @@ class NotificationApi { } Future onDidReceiveLocalNotification( - int id, String title, String body, String payload) => + int id, String? title, String? body, String? payload) => Future.value(); - Future selectNotification(String payload) => Future.value(); + Future selectNotification(String? payload) => Future.value(); } /// @@ -83,7 +84,8 @@ class NotificationApi { /// class VoidNotificationApi implements NotificationApi { @override - FlutterLocalNotificationsPlugin get _localNotificationsPlugin => null; + FlutterLocalNotificationsPlugin get _localNotificationsPlugin => + throw UnimplementedError(); @override Future initialize() { @@ -92,17 +94,17 @@ class VoidNotificationApi implements NotificationApi { @override Future onDidReceiveLocalNotification( - int id, String title, String body, String payload) { + int id, String? title, String? body, String? payload) { return Future.value(); } @override - Future selectNotification(String payload) { + Future selectNotification(String? payload) { return Future.value(); } @override - Future showNotification(String title, String message, [int id]) { + Future showNotification(String title, String? message, [int? id]) { return Future.value(); } } diff --git a/lib/common/ui/schedule_entry_type_mappings.dart b/lib/common/ui/schedule_entry_type_mappings.dart index 9c162016..f934ce95 100644 --- a/lib/common/ui/schedule_entry_type_mappings.dart +++ b/lib/common/ui/schedule_entry_type_mappings.dart @@ -25,14 +25,14 @@ final Map scheduleEntryTypeTextMapping = { Color scheduleEntryTypeToColor( BuildContext context, - ScheduleEntryType type, + ScheduleEntryType? type, ) { - return scheduleEntryTypeColorMapping[type](context); + return scheduleEntryTypeColorMapping[type!]!(context); } String scheduleEntryTypeToReadableString( BuildContext context, - ScheduleEntryType type, + ScheduleEntryType? type, ) { - return scheduleEntryTypeTextMapping[type](context); + return scheduleEntryTypeTextMapping[type!]!(context); } diff --git a/lib/common/ui/text_styles.dart b/lib/common/ui/text_styles.dart index f4ec8f22..94b731db 100644 --- a/lib/common/ui/text_styles.dart +++ b/lib/common/ui/text_styles.dart @@ -1,56 +1,56 @@ import 'package:flutter/material.dart'; -TextStyle textStyleDailyScheduleEntryWidgetProfessor(BuildContext context) => +TextStyle? textStyleDailyScheduleEntryWidgetProfessor(BuildContext context) => Theme.of(context).textTheme.subtitle2; TextStyle textStyleDailyScheduleEntryWidgetTitle(BuildContext context) => Theme.of(context) .textTheme - .headline4 - .copyWith(color: Theme.of(context).textTheme.headline6.color); + .headline4! + .copyWith(color: Theme.of(context).textTheme.headline6!.color); TextStyle textStyleDailyScheduleEntryWidgetType(BuildContext context) => - Theme.of(context).textTheme.bodyText2.copyWith( + Theme.of(context).textTheme.bodyText2!.copyWith( fontWeight: FontWeight.w300, letterSpacing: 0.15, ); TextStyle textStyleDailyScheduleEntryWidgetTimeStart(BuildContext context) => - Theme.of(context).textTheme.headline5.copyWith( + Theme.of(context).textTheme.headline5!.copyWith( fontWeight: FontWeight.w600, letterSpacing: 0.15, ); -TextStyle textStyleDailyScheduleEntryWidgetTimeEnd(BuildContext context) => +TextStyle? textStyleDailyScheduleEntryWidgetTimeEnd(BuildContext context) => Theme.of(context).textTheme.subtitle2; TextStyle textStyleDailyScheduleCurrentDate(BuildContext context) => - Theme.of(context).textTheme.headline4.copyWith( - color: Theme.of(context).textTheme.headline5.color, + Theme.of(context).textTheme.headline4!.copyWith( + color: Theme.of(context).textTheme.headline5!.color, ); -TextStyle textStyleDailyScheduleNoEntries(BuildContext context) => +TextStyle? textStyleDailyScheduleNoEntries(BuildContext context) => Theme.of(context).textTheme.headline5; TextStyle textStyleScheduleEntryWidgetTitle(BuildContext context) => - Theme.of(context).textTheme.bodyText1.copyWith( + Theme.of(context).textTheme.bodyText1!.copyWith( fontWeight: FontWeight.normal, ); -TextStyle textStyleScheduleEntryBottomPageTitle(BuildContext context) => +TextStyle? textStyleScheduleEntryBottomPageTitle(BuildContext context) => Theme.of(context).textTheme.subtitle2; -TextStyle textStyleScheduleEntryBottomPageTimeFromTo(BuildContext context) => +TextStyle? textStyleScheduleEntryBottomPageTimeFromTo(BuildContext context) => Theme.of(context).textTheme.caption; -TextStyle textStyleScheduleEntryBottomPageTime(BuildContext context) => +TextStyle? textStyleScheduleEntryBottomPageTime(BuildContext context) => Theme.of(context).textTheme.headline5; TextStyle textStyleScheduleEntryBottomPageType(BuildContext context) => - Theme.of(context).textTheme.bodyText1.copyWith( + Theme.of(context).textTheme.bodyText1!.copyWith( fontWeight: FontWeight.w300, ); TextStyle textStyleScheduleWidgetColumnTitleDay(BuildContext context) => - Theme.of(context).textTheme.subtitle2.copyWith( + Theme.of(context).textTheme.subtitle2!.copyWith( fontWeight: FontWeight.w300, ); diff --git a/lib/common/ui/viewmodels/root_view_model.dart b/lib/common/ui/viewmodels/root_view_model.dart index dd2a360a..f976bcb2 100644 --- a/lib/common/ui/viewmodels/root_view_model.dart +++ b/lib/common/ui/viewmodels/root_view_model.dart @@ -5,25 +5,25 @@ import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; class RootViewModel extends BaseViewModel { final PreferencesProvider _preferencesProvider; - AppTheme _appTheme; + late AppTheme _appTheme; AppTheme get appTheme => _appTheme; - bool _isOnboarding; + late bool _isOnboarding; bool get isOnboarding => _isOnboarding; RootViewModel(this._preferencesProvider); Future loadFromPreferences() async { - var darkMode = await _preferencesProvider.appTheme(); - - _appTheme = darkMode; + _appTheme = await _preferencesProvider.appTheme(); _isOnboarding = await _preferencesProvider.isFirstStart(); notifyListeners("appTheme"); notifyListeners("isOnboarding"); } - Future setAppTheme(AppTheme value) async { + Future setAppTheme(AppTheme? value) async { + if (value == null) return; + await _preferencesProvider.setAppTheme(value); _appTheme = value; notifyListeners("appTheme"); diff --git a/lib/common/ui/widgets/dots_indicator.dart b/lib/common/ui/widgets/dots_indicator.dart index 816e3c79..abcf6f5f 100644 --- a/lib/common/ui/widgets/dots_indicator.dart +++ b/lib/common/ui/widgets/dots_indicator.dart @@ -4,7 +4,8 @@ class DotsIndicator extends StatelessWidget { final int numberSteps; final int currentStep; - const DotsIndicator({Key key, this.numberSteps, this.currentStep}) + const DotsIndicator( + {Key? key, required this.numberSteps, required this.currentStep}) : super(key: key); @override diff --git a/lib/common/ui/widgets/error_display.dart b/lib/common/ui/widgets/error_display.dart index 6cefef8f..6be52a66 100644 --- a/lib/common/ui/widgets/error_display.dart +++ b/lib/common/ui/widgets/error_display.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; class ErrorDisplay extends StatelessWidget { final bool show; - const ErrorDisplay({Key key, this.show}) : super(key: key); + const ErrorDisplay({Key? key, required this.show}) : super(key: key); @override Widget build(BuildContext context) { @@ -24,7 +24,7 @@ class ErrorDisplay extends StatelessWidget { child: Text( L.of(context).noConnectionMessage, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.subtitle2.copyWith( + style: Theme.of(context).textTheme.subtitle2!.copyWith( color: colorNoConnectionForeground(), ), ), diff --git a/lib/common/ui/widgets/title_list_tile.dart b/lib/common/ui/widgets/title_list_tile.dart index c3576a63..82de6d27 100644 --- a/lib/common/ui/widgets/title_list_tile.dart +++ b/lib/common/ui/widgets/title_list_tile.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class TitleListTile extends StatelessWidget { final String title; - const TitleListTile({Key key, this.title}) : super(key: key); + const TitleListTile({Key? key, required this.title}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/common/util/cancelable_mutex.dart b/lib/common/util/cancelable_mutex.dart index 5335981a..3160b39c 100644 --- a/lib/common/util/cancelable_mutex.dart +++ b/lib/common/util/cancelable_mutex.dart @@ -3,8 +3,8 @@ import 'cancellation_token.dart'; class CancelableMutex { final Mutex _mutex = Mutex(); - CancellationToken _token; - CancellationToken get token => _token; + CancellationToken? _token; + CancellationToken? get token => _token; void cancel() { _token?.cancel(); diff --git a/lib/common/util/cancellation_token.dart b/lib/common/util/cancellation_token.dart index 59f097cc..36c5e604 100644 --- a/lib/common/util/cancellation_token.dart +++ b/lib/common/util/cancellation_token.dart @@ -2,7 +2,7 @@ typedef CancellationCallback = void Function(); class CancellationToken { bool _isCancelled = false; - CancellationCallback _callback; + CancellationCallback? _callback; bool isCancelled() { return _isCancelled; @@ -17,10 +17,10 @@ class CancellationToken { void cancel() { _isCancelled = true; - if (_callback != null) _callback(); + _callback?.call(); } - void setCancellationCallback(CancellationCallback callback) { + void setCancellationCallback(CancellationCallback? callback) { _callback = callback; } } diff --git a/lib/common/util/date_utils.dart b/lib/common/util/date_utils.dart index 57b54605..19903804 100644 --- a/lib/common/util/date_utils.dart +++ b/lib/common/util/date_utils.dart @@ -1,20 +1,22 @@ -DateTime toStartOfDay(DateTime dateTime) { +// TODO: [Leptopoda] write extension methods directly on [DateTime] + +DateTime? toStartOfDay(DateTime? dateTime) { return dateTime == null ? null : DateTime(dateTime.year, dateTime.month, dateTime.day, 0, 0, 0); } -DateTime toStartOfMonth(DateTime dateTime) { +DateTime? toStartOfMonth(DateTime? dateTime) { return dateTime == null ? null : DateTime(dateTime.year, dateTime.month, 1, 0, 0, 0); } -DateTime tomorrow(DateTime dateTime) { +DateTime? tomorrow(DateTime? dateTime) { return addDays(dateTime, 1); } -DateTime addDays(DateTime dateTime, int days) { +DateTime? addDays(DateTime? dateTime, int days) { return dateTime == null ? null : DateTime( @@ -28,12 +30,10 @@ DateTime addDays(DateTime dateTime, int days) { } DateTime toMonday(DateTime dateTime) { - return dateTime == null - ? null - : dateTime.subtract(Duration(days: dateTime.weekday - 1)); + return dateTime.subtract(Duration(days: dateTime.weekday - 1)); } -DateTime toPreviousWeek(DateTime dateTime) { +DateTime? toPreviousWeek(DateTime? dateTime) { return dateTime == null ? null : DateTime( @@ -46,7 +46,7 @@ DateTime toPreviousWeek(DateTime dateTime) { ); } -DateTime toNextWeek(DateTime dateTime) { +DateTime? toNextWeek(DateTime? dateTime) { return dateTime == null ? null : DateTime( @@ -60,20 +60,17 @@ DateTime toNextWeek(DateTime dateTime) { } DateTime toNextMonth(DateTime dateTime) { - return dateTime == null - ? null - : DateTime( - dateTime.year, - dateTime.month + 1, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - ); + return DateTime( + dateTime.year, + dateTime.month + 1, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + ); } DateTime toTimeOfDay(DateTime dateTime, int hour, int minute) { - if (dateTime == null) return null; return DateTime( dateTime.year, dateTime.month, @@ -84,8 +81,6 @@ DateTime toTimeOfDay(DateTime dateTime, int hour, int minute) { } DateTime toTimeOfDayInFuture(DateTime dateTime, int hour, int minute) { - if (dateTime == null) return null; - var newDateTime = DateTime( dateTime.year, dateTime.month, @@ -94,7 +89,7 @@ DateTime toTimeOfDayInFuture(DateTime dateTime, int hour, int minute) { minute, ); - if (dateTime.isAfter(newDateTime)) newDateTime = tomorrow(newDateTime); + if (dateTime.isAfter(newDateTime)) newDateTime = tomorrow(newDateTime)!; return newDateTime; } @@ -105,7 +100,7 @@ bool isAtSameDay(DateTime date1, DateTime date2) { date1.day == date2.day; } -DateTime toDayOfWeek(DateTime dateTime, int weekday) { +DateTime? toDayOfWeek(DateTime? dateTime, int weekday) { if (dateTime == null) return null; var startOfWeek = addDays(dateTime, -dateTime.weekday); diff --git a/lib/common/util/platform_util.dart b/lib/common/util/platform_util.dart index a602f6c6..a015d709 100644 --- a/lib/common/util/platform_util.dart +++ b/lib/common/util/platform_util.dart @@ -19,7 +19,7 @@ class PlatformUtil { static Brightness platformBrightness() { final data = MediaQueryData.fromWindow(WidgetsBinding.instance.window); - return data.platformBrightness ?? Brightness.light; + return data.platformBrightness; } static Future initializePortraitLandscapeMode() async { diff --git a/lib/common/util/string_utils.dart b/lib/common/util/string_utils.dart index f308d63b..d910d0e5 100644 --- a/lib/common/util/string_utils.dart +++ b/lib/common/util/string_utils.dart @@ -1,7 +1,9 @@ +// TODO: [Leptopoda] deprecate as dart has it's own function (at least for the above) + String concatStringList(List list, String separator) { var result = ""; - for (var element in list ?? []) { + for (var element in list) { result += element + separator; } @@ -12,10 +14,10 @@ String concatStringList(List list, String separator) { return result; } -String interpolate(String string, List params) { +String interpolate(String string, List params) { String result = string; for (int i = 0; i < params.length; i++) { - result = result.replaceAll('%$i', params[i]); + result = result.replaceAll('%$i', params[i]!); } return result; diff --git a/lib/date_management/business/date_entry_provider.dart b/lib/date_management/business/date_entry_provider.dart index 714d453d..05807f28 100644 --- a/lib/date_management/business/date_entry_provider.dart +++ b/lib/date_management/business/date_entry_provider.dart @@ -13,16 +13,16 @@ class DateEntryProvider { Future> getCachedDateEntries( DateSearchParameters parameters, ) async { - var cachedEntries = []; + List cachedEntries = []; - if (parameters.includeFuture && parameters.includePast) { + if (parameters.includeFuture! && parameters.includePast!) { cachedEntries = await _dateEntryRepository.queryAllDateEntries( parameters.databaseName, parameters.year, ); } else { var now = DateTime.now(); - if (parameters.includeFuture) { + if (parameters.includeFuture!) { var datesAfter = await _dateEntryRepository.queryDateEntriesAfter( parameters.databaseName, parameters.year, @@ -31,7 +31,7 @@ class DateEntryProvider { cachedEntries.addAll(datesAfter); } - if (parameters.includePast) { + if (parameters.includePast!) { var datesBefore = await _dateEntryRepository.queryDateEntriesBefore( parameters.databaseName, parameters.year, @@ -41,8 +41,9 @@ class DateEntryProvider { } } - cachedEntries.sort((DateEntry a1, DateEntry a2) => - a1?.start?.compareTo(a2?.start)); + cachedEntries.sort( + (DateEntry a1, DateEntry a2) => a1.start.compareTo(a2.start), + ); print("Read cached ${cachedEntries.length} date entries"); @@ -51,7 +52,7 @@ class DateEntryProvider { Future> getDateEntries( DateSearchParameters parameters, - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ) async { var updatedEntries = await _dateEntryService.queryAllDates( parameters, @@ -82,11 +83,11 @@ class DateEntryProvider { continue; } - if (dateEntry.start.isBefore(now) && !parameters.includePast) { + if (dateEntry.start.isBefore(now) && !parameters.includePast!) { continue; } - if (dateEntry.end.isAfter(now) && !parameters.includeFuture) { + if (dateEntry.end.isAfter(now) && !parameters.includeFuture!) { continue; } diff --git a/lib/date_management/data/calendar_access.dart b/lib/date_management/data/calendar_access.dart index d57bdec7..cf6c852c 100644 --- a/lib/date_management/data/calendar_access.dart +++ b/lib/date_management/data/calendar_access.dart @@ -5,8 +5,6 @@ import 'package:dhbwstudentapp/date_management/model/date_entry.dart'; import 'package:flutter/services.dart'; import 'package:timezone/timezone.dart' as tz; - - enum CalendarPermission { PermissionGranted, PermissionDenied, @@ -19,14 +17,13 @@ enum CalendarPermission { class CalendarAccess { final DeviceCalendarPlugin _deviceCalendarPlugin = DeviceCalendarPlugin(); - Future requestCalendarPermission() async { try { var permissionsGranted = await _deviceCalendarPlugin.hasPermissions(); - if (permissionsGranted.isSuccess && !permissionsGranted.data) { + if (permissionsGranted.isSuccess && !permissionsGranted.data!) { permissionsGranted = await _deviceCalendarPlugin.requestPermissions(); - if (!permissionsGranted.isSuccess || !permissionsGranted.data) { + if (!permissionsGranted.isSuccess || !permissionsGranted.data!) { return CalendarPermission.PermissionDenied; } } @@ -42,8 +39,8 @@ class CalendarAccess { Future> queryWriteableCalendars() async { final calendarsResult = await _deviceCalendarPlugin.retrieveCalendars(); var writeableCalendars = []; - for (var calendar in calendarsResult?.data ?? []) { - if (!calendar.isReadOnly) { + for (var calendar in calendarsResult.data ?? []) { + if (!calendar.isReadOnly!) { writeableCalendars.add(calendar); } } @@ -53,12 +50,12 @@ class CalendarAccess { Future addOrUpdateDates( List dateEntries, - Calendar calendar, + Calendar? calendar, ) async { - if ((dateEntries ?? []).isEmpty) return; + if (dateEntries.isEmpty) return; var existingEvents = - await _getExistingEventsFromCalendar(dateEntries, calendar); + await _getExistingEventsFromCalendar(dateEntries, calendar!); for (var entry in dateEntries) { await _addOrUpdateEntry(existingEvents, entry, calendar); @@ -82,7 +79,6 @@ class CalendarAccess { end = entry.end; } - return await _deviceCalendarPlugin.createOrUpdateEvent(Event( calendar.id, location: entry.room, @@ -95,11 +91,13 @@ class CalendarAccess { )); } - String _getIdOfExistingEvent(List existingEvents, DateEntry entry) { + String? _getIdOfExistingEvent(List existingEvents, DateEntry entry) { var existingEvent = existingEvents - .where((element) => (element.title == entry.description && element.start.toUtc().isAtSameMomentAs(entry.start.toUtc()))) + .where((element) => (element.title == entry.description && + (element.start?.toUtc().isAtSameMomentAs(entry.start.toUtc()) ?? + false))) .toList(); - String id; + String? id; if (existingEvent.isNotEmpty) { id = existingEvent[0].eventId; @@ -122,7 +120,7 @@ class CalendarAccess { var existingEvents = []; if (existingEventsResult.isSuccess) { - existingEvents = existingEventsResult.data.toList(); + existingEvents = existingEventsResult.data!.toList(); } return existingEvents; } @@ -131,7 +129,7 @@ class CalendarAccess { var firstEntry = entries[0]; for (var entry in entries) { - if (entry.end.isBefore(firstEntry?.end)) { + if (entry.end.isBefore(firstEntry.end)) { firstEntry = entry; } } diff --git a/lib/date_management/data/date_entry_entity.dart b/lib/date_management/data/date_entry_entity.dart index 20302338..b3b23a75 100644 --- a/lib/date_management/data/date_entry_entity.dart +++ b/lib/date_management/data/date_entry_entity.dart @@ -2,22 +2,17 @@ import 'package:dhbwstudentapp/common/data/database_entity.dart'; import 'package:dhbwstudentapp/date_management/model/date_entry.dart'; class DateEntryEntity extends DatabaseEntity { - DateEntry _dateEntry; + // TODO: [Leptopoda] make non null + DateEntry? _dateEntry; - DateEntryEntity.fromModel(DateEntry dateEntry) { - _dateEntry = dateEntry; - } + DateEntryEntity.fromModel(DateEntry dateEntry) : _dateEntry = dateEntry; DateEntryEntity.fromMap(Map map) { fromMap(map); } - @override void fromMap(Map map) { - DateTime date; - if (map["date"] != null) { - date = DateTime.fromMillisecondsSinceEpoch(map["date"]); - } + var date = DateTime.fromMillisecondsSinceEpoch(map["date"]); _dateEntry = DateEntry( comment: map["comment"], @@ -26,22 +21,24 @@ class DateEntryEntity extends DatabaseEntity { databaseName: map["databaseName"], start: date, end: date, - room: map["room"] + room: map["room"], ); } @override Map toMap() { + assert(_dateEntry != null); return { - "date": _dateEntry.start?.millisecondsSinceEpoch ?? 0, - "comment": _dateEntry.comment ?? "", - "description": _dateEntry.description ?? "", - "year": _dateEntry.year ?? "", - "databaseName": _dateEntry.databaseName ?? "" + "date": _dateEntry!.start.millisecondsSinceEpoch, + "comment": _dateEntry!.comment, + "description": _dateEntry!.description, + "year": _dateEntry!.year, + "databaseName": _dateEntry!.databaseName, }; } - DateEntry asDateEntry() => _dateEntry; + DateEntry asDateEntry() => _dateEntry!; + // TODO: [Leptopoda] make getter or even constant. static String tableName() => "DateEntries"; } diff --git a/lib/date_management/data/date_entry_repository.dart b/lib/date_management/data/date_entry_repository.dart index 7a4f675b..7fc21c7b 100644 --- a/lib/date_management/data/date_entry_repository.dart +++ b/lib/date_management/data/date_entry_repository.dart @@ -8,8 +8,8 @@ class DateEntryRepository { DateEntryRepository(this._database); Future> queryAllDateEntries( - String databaseName, - String year, + String? databaseName, + String? year, ) async { var rows = await _database.queryRows( DateEntryEntity.tableName(), @@ -20,7 +20,7 @@ class DateEntryRepository { return _rowsToDateEntries(rows); } - Future> queryDateEntriesBetween( + Future> queryDateEntriesBetween( String databaseName, String year, DateTime start, @@ -41,8 +41,8 @@ class DateEntryRepository { } Future> queryDateEntriesAfter( - String databaseName, - String year, + String? databaseName, + String? year, DateTime date, ) async { var rows = await _database.queryRows( @@ -59,8 +59,8 @@ class DateEntryRepository { } Future> queryDateEntriesBefore( - String databaseName, - String year, + String? databaseName, + String? year, DateTime date, ) async { var rows = await _database.queryRows( @@ -88,8 +88,8 @@ class DateEntryRepository { } Future deleteAllDateEntries( - String databaseName, - String year, + String? databaseName, + String? year, ) async { await _database.deleteWhere(DateEntryEntity.tableName(), where: "databaseName=? AND year=?", diff --git a/lib/date_management/model/date_entry.dart b/lib/date_management/model/date_entry.dart index 02ba7271..17e4ce98 100644 --- a/lib/date_management/model/date_entry.dart +++ b/lib/date_management/model/date_entry.dart @@ -5,15 +5,20 @@ class DateEntry { final String databaseName; final DateTime start; final DateTime end; - final String room; + final String? room; DateEntry({ - this.description, - this.year, - this.comment, - this.databaseName, - this.start, - this.end, - this.room - }); + String? description, + String? year, + String? comment, + String? databaseName, + DateTime? start, + DateTime? end, + this.room, + }) : this.start = start ?? DateTime.fromMicrosecondsSinceEpoch(0), + this.end = end ?? DateTime.fromMicrosecondsSinceEpoch(0), + this.comment = comment ?? "", + this.description = description ?? "", + this.year = year ?? "", + this.databaseName = databaseName ?? ""; } diff --git a/lib/date_management/model/date_search_parameters.dart b/lib/date_management/model/date_search_parameters.dart index a6a1dfc5..f686fc9f 100644 --- a/lib/date_management/model/date_search_parameters.dart +++ b/lib/date_management/model/date_search_parameters.dart @@ -1,8 +1,8 @@ class DateSearchParameters { - bool includePast; - bool includeFuture; - String year; - String databaseName; + bool? includePast; + bool? includeFuture; + String? year; + String? databaseName; DateSearchParameters( this.includePast, diff --git a/lib/date_management/service/date_management_service.dart b/lib/date_management/service/date_management_service.dart index a192677a..0e5d11fa 100644 --- a/lib/date_management/service/date_management_service.dart +++ b/lib/date_management/service/date_management_service.dart @@ -7,7 +7,7 @@ import 'package:dhbwstudentapp/dualis/service/session.dart'; class DateManagementService { Future> queryAllDates( DateSearchParameters parameters, - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ) async { var queryResult = await Session().get( _buildRequestUrl(parameters), @@ -28,7 +28,7 @@ class DateManagementService { url += "&sel_typ=pub"; url += "&sel_jahrgang= ${parameters.year ?? "*"}"; url += "&sel_bezeichnung=*"; - url += "&selection=${parameters.includePast ? "**" : "*"}"; + url += "&selection=${parameters.includePast! ? "**" : "*"}"; url += "&permission=show"; url += "&sessionid="; url += "&user=nobody"; diff --git a/lib/date_management/service/parsing/all_dates_extract.dart b/lib/date_management/service/parsing/all_dates_extract.dart index 3db64a4f..828c75bb 100644 --- a/lib/date_management/service/parsing/all_dates_extract.dart +++ b/lib/date_management/service/parsing/all_dates_extract.dart @@ -5,7 +5,8 @@ import 'package:intl/intl.dart'; // TODO: Parse exception to common module class AllDatesExtract { - List extractAllDates(String body, String databaseName) { + List extractAllDates(String? body, String? databaseName) { + if (body == null) return []; try { return _extractAllDates(body, databaseName); } catch (e, trace) { @@ -14,7 +15,7 @@ class AllDatesExtract { } } - List _extractAllDates(String body, String databaseName) { + List _extractAllDates(String body, String? databaseName) { body = body.replaceAll(new RegExp("<(br|BR)[ /]*>"), "\n"); var document = parse(body); @@ -24,7 +25,7 @@ class AllDatesExtract { var dateEntries = []; for (var a in dateContainingElement.nodes.sublist(0)) { - var text = a.text; + var text = a.text!; var lines = text.split("\n"); @@ -38,12 +39,12 @@ class AllDatesExtract { } dateEntries - .sort((DateEntry e1, DateEntry e2) => e1.start?.compareTo(e2.start)); + .sort((DateEntry e1, DateEntry e2) => e1.start.compareTo(e2.start)); return dateEntries; } - DateEntry _parseDateEntryLine(String line, String databaseName) { + DateEntry? _parseDateEntryLine(String line, String? databaseName) { var parts = line.split(';'); if (parts.length != 5) { @@ -64,7 +65,7 @@ class AllDatesExtract { end: date); } - DateTime _parseDateTime(String date, String time) { + DateTime? _parseDateTime(String date, String time) { if (time == "24:00") { time = "00:00"; } diff --git a/lib/date_management/ui/calendar_export_page.dart b/lib/date_management/ui/calendar_export_page.dart index 6ee2188d..9a439d32 100644 --- a/lib/date_management/ui/calendar_export_page.dart +++ b/lib/date_management/ui/calendar_export_page.dart @@ -15,32 +15,25 @@ class CalendarExportPage extends StatefulWidget { final bool isCalendarSyncEnabled; const CalendarExportPage( - {Key key, - this.entriesToExport, + {Key? key, + required this.entriesToExport, this.isCalendarSyncWidget = false, this.isCalendarSyncEnabled = false}) : super(key: key); @override - _CalendarExportPageState createState() => _CalendarExportPageState( - entriesToExport, isCalendarSyncWidget, isCalendarSyncEnabled); + _CalendarExportPageState createState() => _CalendarExportPageState(); } class _CalendarExportPageState extends State { - final List entriesToExport; - final bool isCalendarSyncWidget; - final bool isCalendarSyncEnabled; - CalendarExportViewModel viewModel; - - _CalendarExportPageState(this.entriesToExport, this.isCalendarSyncWidget, - this.isCalendarSyncEnabled); + late CalendarExportViewModel viewModel; @override void initState() { super.initState(); - viewModel = CalendarExportViewModel(entriesToExport, CalendarAccess(), - KiwiContainer().resolve()); + viewModel = CalendarExportViewModel(widget.entriesToExport, + CalendarAccess(), KiwiContainer().resolve()); viewModel.setOnPermissionDeniedCallback(() { Navigator.of(context).pop(); }); @@ -57,7 +50,7 @@ class _CalendarExportPageState extends State { actionsIconTheme: Theme.of(context).iconTheme, elevation: 0, iconTheme: Theme.of(context).iconTheme, - title: Text(this.isCalendarSyncWidget + title: Text(widget.isCalendarSyncWidget ? L.of(context).calendarSyncPageTitle : L.of(context).dateManagementExportToCalendar), toolbarTextStyle: Theme.of(context).textTheme.bodyText2, @@ -69,7 +62,7 @@ class _CalendarExportPageState extends State { padding: const EdgeInsets.all(24), child: Align( alignment: Alignment.centerLeft, - child: Text(this.isCalendarSyncWidget + child: Text(widget.isCalendarSyncWidget ? L.of(context).calendarSyncPageSubtitle : L.of(context).dateManagementExportToCalendarDescription), ), @@ -89,12 +82,13 @@ class _CalendarExportPageState extends State { Widget _buildCalendarList() { return Expanded( child: PropertyChangeConsumer( - builder: (BuildContext context, CalendarExportViewModel viewModel, _) => - ListView.builder( - itemCount: viewModel.calendars.length, + builder: + (BuildContext context, CalendarExportViewModel? viewModel, _) => + ListView.builder( + itemCount: viewModel!.calendars.length, itemBuilder: (BuildContext context, int index) { - var isSelected = viewModel.selectedCalendar?.id == - viewModel.calendars[index]?.id; + var isSelected = + viewModel.selectedCalendar?.id == viewModel.calendars[index].id; return _buildCalendarListEntry( viewModel.calendars[index], @@ -109,21 +103,21 @@ class _CalendarExportPageState extends State { Widget _buildStopCalendarSyncBtn() { // Dont display the "Synchronisation beenden" button, //if synchronization is not enabled or if it is not the right page - if (!this.isCalendarSyncWidget) return SizedBox(); + if (!widget.isCalendarSyncWidget) return SizedBox(); return PropertyChangeProvider( value: viewModel, child: Column( children: [ Container( - decoration: this.isCalendarSyncEnabled ? null : null, + decoration: widget.isCalendarSyncEnabled ? null : null, child: ListTile( - enabled: this.isCalendarSyncEnabled ? true : false, + enabled: widget.isCalendarSyncEnabled ? true : false, title: Text( L.of(context).calendarSyncPageEndSync.toUpperCase(), textAlign: TextAlign.center, style: TextStyle( - color: this.isCalendarSyncEnabled + color: widget.isCalendarSyncEnabled ? ColorPalettes.main : Theme.of(context).disabledColor, fontSize: 14), @@ -163,9 +157,9 @@ class _CalendarExportPageState extends State { height: 24, decoration: BoxDecoration( shape: BoxShape.circle, - color: isSelected ? Color(calendar.color) : Colors.transparent, + color: isSelected ? Color(calendar.color!) : Colors.transparent, border: Border.all( - color: Color(calendar.color), + color: Color(calendar.color!), width: 4, ), ), @@ -181,7 +175,7 @@ class _CalendarExportPageState extends State { ), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 0, 0), - child: Text(calendar.name), + child: Text(calendar.name!), ), ], ), @@ -191,8 +185,8 @@ class _CalendarExportPageState extends State { Widget _buildExportButton() { return PropertyChangeConsumer( - builder: (BuildContext context, CalendarExportViewModel viewModel, _) => - viewModel.isExporting + builder: (BuildContext context, CalendarExportViewModel? viewModel, _) => + viewModel!.isExporting ? const Padding( padding: EdgeInsets.fromLTRB(8, 8, 8, 15), child: SizedBox( @@ -214,7 +208,7 @@ class _CalendarExportPageState extends State { borderRadius: BorderRadius.all(Radius.circular(4.0)), ), title: Text( - this.isCalendarSyncWidget + widget.isCalendarSyncWidget ? L .of(context) .calendarSyncPageBeginSync @@ -231,7 +225,7 @@ class _CalendarExportPageState extends State { ), onTap: viewModel.canExport ? () async { - if (this.isCalendarSyncWidget) { + if (widget.isCalendarSyncWidget) { var preferencesProvider = KiwiContainer() .resolve(); preferencesProvider.setSelectedCalendar( diff --git a/lib/date_management/ui/date_management_navigation_entry.dart b/lib/date_management/ui/date_management_navigation_entry.dart index 57856113..04806a09 100644 --- a/lib/date_management/ui/date_management_navigation_entry.dart +++ b/lib/date_management/ui/date_management_navigation_entry.dart @@ -1,5 +1,4 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/date_management/ui/calendar_export_page.dart'; import 'package:dhbwstudentapp/date_management/ui/date_management_page.dart'; import 'package:dhbwstudentapp/date_management/ui/viewmodels/date_management_view_model.dart'; @@ -10,13 +9,10 @@ import 'package:flutter/material.dart'; import 'package:kiwi/kiwi.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; -class DateManagementNavigationEntry extends NavigationEntry { - DateManagementViewModel _viewModel; - +class DateManagementNavigationEntry + extends NavigationEntry { @override - Widget icon(BuildContext context) { - return Icon(Icons.date_range); - } + Icon icon = Icon(Icons.date_range); @override String title(BuildContext context) { @@ -24,20 +20,15 @@ class DateManagementNavigationEntry extends NavigationEntry { } @override - BaseViewModel initViewModel() { - if (_viewModel == null) { - _viewModel = DateManagementViewModel( - KiwiContainer().resolve(), - KiwiContainer().resolve(), - ); - } - - return _viewModel; + DateManagementViewModel initViewModel() { + return DateManagementViewModel( + KiwiContainer().resolve(), + KiwiContainer().resolve(), + ); } @override List appBarActions(BuildContext context) { - initViewModel(); return [ IconButton( icon: Icon(Icons.help_outline), @@ -47,17 +38,19 @@ class DateManagementNavigationEntry extends NavigationEntry { tooltip: L.of(context).helpButtonTooltip, ), PropertyChangeProvider( - value: _viewModel, + value: model, child: PropertyChangeConsumer( - builder: - (BuildContext context, DateManagementViewModel viewModel, _) => - PopupMenuButton( + builder: (BuildContext context, DateManagementViewModel? _, __) => + PopupMenuButton( onSelected: (i) async { - await NavigatorKey.rootKey.currentState.push(MaterialPageRoute( + await NavigatorKey.rootKey.currentState!.push( + MaterialPageRoute( builder: (BuildContext context) => CalendarExportPage( - entriesToExport: viewModel.allDates, - ), - settings: RouteSettings(name: "settings"))); + entriesToExport: model.allDates!, + ), + settings: RouteSettings(name: "settings"), + ), + ); }, itemBuilder: (BuildContext context) { return [ diff --git a/lib/date_management/ui/date_management_page.dart b/lib/date_management/ui/date_management_page.dart index 5ace61e4..3e2d124a 100644 --- a/lib/date_management/ui/date_management_page.dart +++ b/lib/date_management/ui/date_management_page.dart @@ -1,5 +1,4 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/common/ui/widgets/error_display.dart'; import 'package:dhbwstudentapp/common/util/date_utils.dart'; import 'package:dhbwstudentapp/date_management/model/date_entry.dart'; @@ -14,7 +13,8 @@ import 'package:provider/provider.dart'; class DateManagementPage extends StatelessWidget { @override Widget build(BuildContext context) { - DateManagementViewModel viewModel = Provider.of(context); + DateManagementViewModel viewModel = + Provider.of(context); return PropertyChangeProvider( value: viewModel, @@ -47,17 +47,17 @@ class DateManagementPage extends StatelessWidget { child: PropertyChangeConsumer( builder: ( BuildContext context, - DateManagementViewModel model, + DateManagementViewModel? model, _, ) => AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: Column( - key: ValueKey( - viewModel?.dateSearchParameters?.toString() ?? ""), + key: + ValueKey(viewModel.dateSearchParameters.toString()), crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _buildAllDatesDataTable(model, context), + _buildAllDatesDataTable(model!, context), ], )), ), @@ -94,17 +94,21 @@ class DateManagementPage extends StatelessWidget { BuildContext context, ) { var dataRows = []; - for (DateEntry dateEntry in model?.allDates ?? []) { + for (DateEntry dateEntry in model.allDates ?? []) { dataRows.add( DataRow( cells: [ DataCell( - Text(dateEntry.description, - style: dateEntry.end.isBefore(DateTime.now()) - ? TextStyle(decoration: TextDecoration.lineThrough) - : null), onTap: () { - showDateEntryDetailBottomSheet(context, dateEntry); - }), + Text( + dateEntry.description, + style: dateEntry.end.isBefore(DateTime.now()) + ? TextStyle(decoration: TextDecoration.lineThrough) + : null, + ), + onTap: () { + showDateEntryDetailBottomSheet(context, dateEntry); + }, + ), DataCell( Column( mainAxisAlignment: MainAxisAlignment.center, @@ -138,7 +142,7 @@ class DateManagementPage extends StatelessWidget { return dataRows; } - void showDateEntryDetailBottomSheet(BuildContext context, DateEntry entry) { + void showDateEntryDetailBottomSheet(BuildContext context, DateEntry? entry) { showModalBottomSheet( useRootNavigator: true, context: context, @@ -156,10 +160,10 @@ class DateManagementPage extends StatelessWidget { properties: const [ "updateFailed", ], - builder: (BuildContext context, DateManagementViewModel model, - Set properties) => + builder: (BuildContext context, DateManagementViewModel? model, + Set? properties) => ErrorDisplay( - show: model.updateFailed, + show: model!.updateFailed, ), ); } diff --git a/lib/date_management/ui/viewmodels/calendar_export_view_model.dart b/lib/date_management/ui/viewmodels/calendar_export_view_model.dart index 7bbf6d6a..b4f9a78a 100644 --- a/lib/date_management/ui/viewmodels/calendar_export_view_model.dart +++ b/lib/date_management/ui/viewmodels/calendar_export_view_model.dart @@ -11,20 +11,21 @@ class CalendarExportViewModel extends BaseViewModel { final PreferencesProvider preferencesProvider; final List _entriesToExport; - OnPermissionDenied _onPermissionDenied; + OnPermissionDenied? _onPermissionDenied; - List _calendars; - List get calendars => _calendars ?? []; + List? _calendars; + List get calendars => _calendars ??= []; - Calendar _selectedCalendar; - Calendar get selectedCalendar => _selectedCalendar; + Calendar? _selectedCalendar; + Calendar? get selectedCalendar => _selectedCalendar; bool get canExport => _selectedCalendar != null; bool _isExporting = false; bool get isExporting => _isExporting; - CalendarExportViewModel(this._entriesToExport, this.calendarAccess, this.preferencesProvider) { + CalendarExportViewModel( + this._entriesToExport, this.calendarAccess, this.preferencesProvider) { loadCalendars(); } @@ -41,12 +42,12 @@ class CalendarExportViewModel extends BaseViewModel { notifyListeners("_calendars"); } - void loadSelectedCalendar() async{ + void loadSelectedCalendar() async { _selectedCalendar = await preferencesProvider.getSelectedCalendar(); notifyListeners("selectedCalendar"); } - void resetSelectedCalendar() async{ + void resetSelectedCalendar() async { await preferencesProvider.setSelectedCalendar(null); this.loadCalendars(); } diff --git a/lib/date_management/ui/viewmodels/date_management_view_model.dart b/lib/date_management/ui/viewmodels/date_management_view_model.dart index 2c4b1765..87c1437c 100644 --- a/lib/date_management/ui/viewmodels/date_management_view_model.dart +++ b/lib/date_management/ui/viewmodels/date_management_view_model.dart @@ -33,16 +33,16 @@ class DateManagementViewModel extends BaseViewModel { final CancelableMutex _updateMutex = CancelableMutex(); - Timer _errorResetTimer; + Timer? _errorResetTimer; - List _years; + List _years = []; List get years => _years; - String _currentSelectedYear; - String get currentSelectedYear => _currentSelectedYear; + String? _currentSelectedYear; + String? get currentSelectedYear => _currentSelectedYear; - List _allDates; - List get allDates => _allDates; + List? _allDates; + List? get allDates => _allDates; bool _showPassedDates = false; bool get showPassedDates => _showPassedDates; @@ -53,8 +53,8 @@ class DateManagementViewModel extends BaseViewModel { bool _isLoading = false; bool get isLoading => _isLoading; - DateDatabase _currentDateDatabase; - DateDatabase get currentDateDatabase => _currentDateDatabase; + DateDatabase? _currentDateDatabase; + DateDatabase? get currentDateDatabase => _currentDateDatabase; int _dateEntriesKeyIndex = 0; int get dateEntriesKeyIndex => _dateEntriesKeyIndex; @@ -75,8 +75,6 @@ class DateManagementViewModel extends BaseViewModel { } void _buildYearsArray() { - _years = []; - for (var i = 2017; i < DateTime.now().year + 3; i++) { _years.add(i.toString()); } @@ -90,7 +88,8 @@ class DateManagementViewModel extends BaseViewModel { notifyListeners("isLoading"); await _doUpdateDates(); - } catch (_) {} finally { + } catch (_) { + } finally { _isLoading = false; _updateMutex.release(); notifyListeners("isLoading"); @@ -99,11 +98,11 @@ class DateManagementViewModel extends BaseViewModel { Future _doUpdateDates() async { var cachedDateEntries = await _readCachedDateEntries(); - _updateMutex.token.throwIfCancelled(); + _updateMutex.token!.throwIfCancelled(); _setAllDates(cachedDateEntries); var loadedDateEntries = await _readUpdatedDateEntries(); - _updateMutex.token.throwIfCancelled(); + _updateMutex.token!.throwIfCancelled(); if (loadedDateEntries != null) { _setAllDates(loadedDateEntries); @@ -117,23 +116,23 @@ class DateManagementViewModel extends BaseViewModel { notifyListeners("updateFailed"); } - Future> _readUpdatedDateEntries() async { + Future?> _readUpdatedDateEntries() async { try { var loadedDateEntries = await _dateEntryProvider.getDateEntries( dateSearchParameters, _updateMutex.token, ); return loadedDateEntries; - } on OperationCancelledException {} on ServiceRequestFailed {} + } on OperationCancelledException { + } on ServiceRequestFailed {} return null; } Future> _readCachedDateEntries() async { - var cachedDateEntries = await _dateEntryProvider.getCachedDateEntries( + return _dateEntryProvider.getCachedDateEntries( dateSearchParameters, ); - return cachedDateEntries; } void _setAllDates(List dateEntries) { @@ -143,24 +142,31 @@ class DateManagementViewModel extends BaseViewModel { notifyListeners("allDates"); } - void setShowPassedDates(bool value) { + // TODO [Leptopoda]: use setters + void setShowPassedDates(bool? value) { + if (value == null) return; + _showPassedDates = value; notifyListeners("showPassedDates"); } - void setShowFutureDates(bool value) { + void setShowFutureDates(bool? value) { + if (value == null) return; + _showFutureDates = value; notifyListeners("showFutureDates"); } - void setCurrentDateDatabase(DateDatabase database) { + void setCurrentDateDatabase(DateDatabase? database) { _currentDateDatabase = database; notifyListeners("currentDateDatabase"); _preferencesProvider.setLastViewedDateEntryDatabase(database?.id); } - void setCurrentSelectedYear(String year) { + void setCurrentSelectedYear(String? year) { + if (year == null) return; + _currentSelectedYear = year; notifyListeners("currentSelectedYear"); @@ -183,19 +189,13 @@ class DateManagementViewModel extends BaseViewModel { } var year = await _preferencesProvider.getLastViewedDateEntryYear(); - if (year != null) { - setCurrentSelectedYear(year); - } else { - setCurrentSelectedYear(years[0]); - } + setCurrentSelectedYear(year ?? years[0]); await updateDates(); } void _cancelErrorInFuture() async { - if (_errorResetTimer != null) { - _errorResetTimer.cancel(); - } + _errorResetTimer?.cancel(); _errorResetTimer = Timer( const Duration(seconds: 5), diff --git a/lib/date_management/ui/widgets/date_detail_bottom_sheet.dart b/lib/date_management/ui/widgets/date_detail_bottom_sheet.dart index 679eba0e..981ab641 100644 --- a/lib/date_management/ui/widgets/date_detail_bottom_sheet.dart +++ b/lib/date_management/ui/widgets/date_detail_bottom_sheet.dart @@ -6,16 +6,16 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class DateDetailBottomSheet extends StatelessWidget { - final DateEntry dateEntry; + final DateEntry? dateEntry; - const DateDetailBottomSheet({Key key, this.dateEntry}) : super(key: key); + const DateDetailBottomSheet({Key? key, this.dateEntry}) : super(key: key); @override Widget build(BuildContext context) { var date = DateFormat.yMd(L.of(context).locale.languageCode) - .format(dateEntry.start); + .format(dateEntry!.start); var time = DateFormat.Hm(L.of(context).locale.languageCode) - .format(dateEntry.start); + .format(dateEntry!.start); return Container( height: 400, @@ -46,7 +46,7 @@ class DateDetailBottomSheet extends StatelessWidget { children: [ Expanded( child: Text( - dateEntry.description, + dateEntry!.description, style: Theme.of(context).textTheme.headline5, ), ), @@ -60,7 +60,7 @@ class DateDetailBottomSheet extends StatelessWidget { softWrap: true, style: Theme.of(context).textTheme.subtitle2, ), - isAtMidnight(dateEntry.start) + isAtMidnight(dateEntry!.start) ? Container() : Text( time, @@ -75,7 +75,7 @@ class DateDetailBottomSheet extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), child: Text( - dateEntry.comment, + dateEntry!.comment, ), ), ], diff --git a/lib/date_management/ui/widgets/date_filter_options.dart b/lib/date_management/ui/widgets/date_filter_options.dart index de063007..1eaf161a 100644 --- a/lib/date_management/ui/widgets/date_filter_options.dart +++ b/lib/date_management/ui/widgets/date_filter_options.dart @@ -6,19 +6,16 @@ import 'package:flutter/material.dart'; class DateFilterOptions extends StatefulWidget { final DateManagementViewModel viewModel; - const DateFilterOptions({Key key, this.viewModel}) : super(key: key); + const DateFilterOptions({Key? key, required this.viewModel}) + : super(key: key); @override - _DateFilterOptionsState createState() => _DateFilterOptionsState(viewModel); + _DateFilterOptionsState createState() => _DateFilterOptionsState(); } class _DateFilterOptionsState extends State { - final DateManagementViewModel viewModel; - bool _isExpanded = false; - _DateFilterOptionsState(this.viewModel); - @override Widget build(BuildContext context) { return AnimatedCrossFade( @@ -81,31 +78,31 @@ class _DateFilterOptionsState extends State { Widget _buildCollapsedChips() { var chips = []; - if (viewModel.showPassedDates && viewModel.showFutureDates) { + if (widget.viewModel.showPassedDates && widget.viewModel.showFutureDates) { chips.add(Chip( label: Text(L.of(context).dateManagementChipFutureAndPast), visualDensity: VisualDensity.compact, )); - } else if (viewModel.showFutureDates) { + } else if (widget.viewModel.showFutureDates) { chips.add(Chip( label: Text(L.of(context).dateManagementChipOnlyFuture), visualDensity: VisualDensity.compact, )); - } else if (viewModel.showPassedDates) { + } else if (widget.viewModel.showPassedDates) { chips.add(Chip( label: Text(L.of(context).dateManagementChipOnlyPassed), visualDensity: VisualDensity.compact, )); } - if (viewModel.currentSelectedYear != null) { + if (widget.viewModel.currentSelectedYear != null) { chips.add(Chip( - label: Text(viewModel.currentSelectedYear), + label: Text(widget.viewModel.currentSelectedYear!), visualDensity: VisualDensity.compact, )); } - var database = viewModel.currentDateDatabase?.displayName ?? ""; + var database = widget.viewModel.currentDateDatabase?.displayName ?? ""; if (database != "") { chips.add(Chip( label: Text(database), @@ -147,10 +144,8 @@ class _DateFilterOptionsState extends State { flex: 1, child: DropdownButton( isExpanded: true, - value: viewModel.currentDateDatabase, - onChanged: (value) { - viewModel.setCurrentDateDatabase(value); - }, + value: widget.viewModel.currentDateDatabase, + onChanged: widget.viewModel.setCurrentDateDatabase, items: _buildDatabaseMenuItems(), ), ), @@ -171,10 +166,8 @@ class _DateFilterOptionsState extends State { flex: 1, child: DropdownButton( isExpanded: true, - value: viewModel.currentSelectedYear, - onChanged: (value) { - viewModel.setCurrentSelectedYear(value); - }, + value: widget.viewModel.currentSelectedYear, + onChanged: widget.viewModel.setCurrentSelectedYear, items: _buildYearsMenuItems(), ), ), @@ -182,19 +175,15 @@ class _DateFilterOptionsState extends State { ), CheckboxListTile( title: Text(L.of(context).dateManagementCheckBoxFutureDates), - value: viewModel.showFutureDates, + value: widget.viewModel.showFutureDates, dense: true, - onChanged: (bool value) { - viewModel.setShowFutureDates(value); - }, + onChanged: widget.viewModel.setShowFutureDates, ), CheckboxListTile( title: Text(L.of(context).dateManagementCheckBoxPassedDates), - value: viewModel.showPassedDates, + value: widget.viewModel.showPassedDates, dense: true, - onChanged: (bool value) { - viewModel.setShowPassedDates(value); - }, + onChanged: widget.viewModel.setShowPassedDates, ), ], ), @@ -211,7 +200,7 @@ class _DateFilterOptionsState extends State { _isExpanded = false; }); - viewModel.updateDates(); + widget.viewModel.updateDates(); }, ), ), @@ -223,7 +212,7 @@ class _DateFilterOptionsState extends State { List> _buildYearsMenuItems() { var yearMenuItems = >[]; - for (var year in viewModel.years) { + for (var year in widget.viewModel.years) { yearMenuItems.add(DropdownMenuItem( child: Text(year), value: year, @@ -235,7 +224,7 @@ class _DateFilterOptionsState extends State { List> _buildDatabaseMenuItems() { var databaseMenuItems = >[]; - for (var database in viewModel.allDateDatabases) { + for (var database in widget.viewModel.allDateDatabases) { databaseMenuItems.add(DropdownMenuItem( child: Text(database.displayName), value: database, diff --git a/lib/dualis/model/credentials.dart b/lib/dualis/model/credentials.dart index 81eb7831..d409e54f 100644 --- a/lib/dualis/model/credentials.dart +++ b/lib/dualis/model/credentials.dart @@ -1,10 +1,42 @@ -class Credentials { - final String password; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +class Credentials extends Equatable { final String username; + final String password; + + const Credentials(this.username, this.password); + + @override + List get props => [username, password]; +} - Credentials(this.username, this.password); +class CredentialsEditingController { + final _usernameController = TextEditingController(); + final _passwordController = TextEditingController(); - bool allFieldsFilled() { - return password != null && username != null; + CredentialsEditingController([Credentials? credentials]) { + if (credentials != null) { + _usernameController.text = credentials.username; + _passwordController.text = credentials.password; + } } + + set credentials(Credentials credentials) { + _usernameController.text = credentials.username; + _passwordController.text = credentials.password; + } + + void dispose() { + _usernameController.dispose(); + _passwordController.dispose(); + } + + TextEditingController get username => _usernameController; + TextEditingController get password => _passwordController; + + Credentials get credentials => Credentials( + _usernameController.text, + _passwordController.text, + ); } diff --git a/lib/dualis/model/exam.dart b/lib/dualis/model/exam.dart index e8947ddb..22cf8f93 100644 --- a/lib/dualis/model/exam.dart +++ b/lib/dualis/model/exam.dart @@ -7,9 +7,9 @@ enum ExamState { } class Exam { - final String name; + final String? name; final ExamGrade grade; - final String semester; + final String? semester; final ExamState state; Exam( diff --git a/lib/dualis/model/exam_grade.dart b/lib/dualis/model/exam_grade.dart index a494edac..0759ddd1 100644 --- a/lib/dualis/model/exam_grade.dart +++ b/lib/dualis/model/exam_grade.dart @@ -7,7 +7,7 @@ enum ExamGradeState { class ExamGrade { ExamGradeState state; - String gradeValue; + String? gradeValue; ExamGrade.failed() : state = ExamGradeState.Failed, @@ -23,7 +23,7 @@ class ExamGrade { ExamGrade.graded(this.gradeValue) : state = ExamGradeState.Graded; - static ExamGrade fromString(String grade) { + static ExamGrade fromString(String? grade) { if (grade == "noch nicht gesetzt" || grade == "") { return ExamGrade.notGraded(); } diff --git a/lib/dualis/model/module.dart b/lib/dualis/model/module.dart index d6e4dc56..c2d1d879 100644 --- a/lib/dualis/model/module.dart +++ b/lib/dualis/model/module.dart @@ -2,11 +2,11 @@ import 'package:dhbwstudentapp/dualis/model/exam.dart'; class Module { final List exams; - final String id; - final String name; - final String credits; - final String grade; - final ExamState state; + final String? id; + final String? name; + final String? credits; + final String? grade; + final ExamState? state; Module( this.exams, diff --git a/lib/dualis/model/semester.dart b/lib/dualis/model/semester.dart index 84eecc90..db30676b 100644 --- a/lib/dualis/model/semester.dart +++ b/lib/dualis/model/semester.dart @@ -1,7 +1,7 @@ import 'package:dhbwstudentapp/dualis/model/module.dart'; class Semester { - final String name; + final String? name; final List modules; Semester( diff --git a/lib/dualis/model/study_grades.dart b/lib/dualis/model/study_grades.dart index d57266b2..e5b25ab4 100644 --- a/lib/dualis/model/study_grades.dart +++ b/lib/dualis/model/study_grades.dart @@ -1,8 +1,8 @@ class StudyGrades { - final double gpaTotal; - final double gpaMainModules; - final double creditsTotal; - final double creditsGained; + final double? gpaTotal; + final double? gpaMainModules; + final double? creditsTotal; + final double? creditsGained; StudyGrades( this.gpaTotal, diff --git a/lib/dualis/service/cache_dualis_service_decorator.dart b/lib/dualis/service/cache_dualis_service_decorator.dart index 95a2ec9c..d182de36 100644 --- a/lib/dualis/service/cache_dualis_service_decorator.dart +++ b/lib/dualis/service/cache_dualis_service_decorator.dart @@ -1,4 +1,5 @@ import 'package:dhbwstudentapp/common/util/cancellation_token.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:dhbwstudentapp/dualis/model/module.dart'; import 'package:dhbwstudentapp/dualis/model/semester.dart'; import 'package:dhbwstudentapp/dualis/model/study_grades.dart'; @@ -10,25 +11,24 @@ import 'package:dhbwstudentapp/dualis/service/dualis_service.dart'; class CacheDualisServiceDecorator extends DualisService { final DualisService _service; - List _allModulesCached; - List _allSemesterNamesCached; - Map _semestersCached = {}; - StudyGrades _studyGradesCached; + List? _allModulesCached; + List? _allSemesterNamesCached; + Map _semestersCached = {}; + StudyGrades? _studyGradesCached; CacheDualisServiceDecorator(this._service); @override Future login( - String username, - String password, [ - CancellationToken cancellationToken, + Credentials credentials, [ + CancellationToken? cancellationToken, ]) { - return _service.login(username, password, cancellationToken); + return _service.login(credentials, cancellationToken); } @override Future> queryAllModules([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { if (_allModulesCached != null) { return Future.value(_allModulesCached); @@ -43,8 +43,8 @@ class CacheDualisServiceDecorator extends DualisService { @override Future querySemester( - String name, [ - CancellationToken cancellationToken, + String? name, [ + CancellationToken? cancellationToken, ]) async { if (_semestersCached.containsKey(name)) { return Future.value(_semestersCached[name]); @@ -58,7 +58,7 @@ class CacheDualisServiceDecorator extends DualisService { @override Future> querySemesterNames([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { if (_allSemesterNamesCached != null) { return Future.value(_allSemesterNamesCached); @@ -73,7 +73,7 @@ class CacheDualisServiceDecorator extends DualisService { @override Future queryStudyGrades([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { if (_studyGradesCached != null) { return Future.value(_studyGradesCached); @@ -95,7 +95,7 @@ class CacheDualisServiceDecorator extends DualisService { @override Future logout([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { await _service.logout(cancellationToken); clearCache(); diff --git a/lib/dualis/service/dualis_authentication.dart b/lib/dualis/service/dualis_authentication.dart index 193b0c1b..b7d681ac 100644 --- a/lib/dualis/service/dualis_authentication.dart +++ b/lib/dualis/service/dualis_authentication.dart @@ -1,4 +1,5 @@ import 'package:dhbwstudentapp/common/util/cancellation_token.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:dhbwstudentapp/dualis/service/dualis_service.dart'; import 'package:dhbwstudentapp/dualis/service/dualis_website_model.dart'; import 'package:dhbwstudentapp/dualis/service/parsing/access_denied_extract.dart'; @@ -16,36 +17,28 @@ import 'package:http/http.dart'; class DualisAuthentication { final RegExp _tokenRegex = RegExp("ARGUMENTS=-N([0-9]{15})"); - String _username; - String _password; + Credentials? _credentials; - DualisUrls _dualisUrls; - DualisUrls get dualisUrls => _dualisUrls; + // TODO: [Leptopoda] make singletons :) - String _authToken; - Session _session; + DualisUrls? _dualisUrls; + DualisUrls get dualisUrls => _dualisUrls ??= DualisUrls(); - LoginResult _loginState = LoginResult.LoggedOut; - LoginResult get loginState => _loginState; + String? _authToken; + Session? _session; + Session get session => _session ??= Session(); + + LoginResult? _loginState; + LoginResult get loginState => _loginState ??= LoginResult.LoggedOut; Future login( - String username, - String password, - CancellationToken cancellationToken, + Credentials credentials, + CancellationToken? cancellationToken, ) async { - username = username ?? this._username; - password = password ?? this._password; - - _dualisUrls = dualisUrls ?? DualisUrls(); - - this._username = username; - this._password = password; - - _session = Session(); + _credentials = credentials; var loginResponse = await _makeLoginRequest( - username, - password, + credentials, cancellationToken, ); @@ -68,7 +61,7 @@ class DualisAuthentication { return loginState; } - var redirectPage = await _session.get( + var redirectPage = await session.get( redirectUrl, cancellationToken, ); @@ -83,10 +76,10 @@ class DualisAuthentication { return loginState; } - _updateAccessToken(dualisUrls.mainPageUrl); + _updateAccessToken(dualisUrls.mainPageUrl!); - var mainPage = await _session.get( - dualisUrls.mainPageUrl, + var mainPage = await session.get( + dualisUrls.mainPageUrl!, cancellationToken, ); @@ -100,16 +93,15 @@ class DualisAuthentication { return loginState; } - Future _makeLoginRequest( - String user, - String password, [ - CancellationToken cancellationToken, + Future _makeLoginRequest( + Credentials credentials, [ + CancellationToken? cancellationToken, ]) async { var loginUrl = dualisEndpoint + "/scripts/mgrqispi.dll"; var data = { - "usrname": user, - "pass": password, + "usrname": credentials.username, + "pass": credentials.password, "APPNAME": "CampusNet", "PRGNAME": "LOGINCHECK", "ARGUMENTS": "clino,usrname,pass,menuno,menu_type,browser,platform", @@ -121,7 +113,7 @@ class DualisAuthentication { }; try { - var loginResponse = await _session.rawPost( + var loginResponse = await session.rawPost( loginUrl, data, cancellationToken, @@ -138,15 +130,19 @@ class DualisAuthentication { /// This method handles the authentication cookie and token. If the session /// timed out, it will renew the session by logging in again /// - Future authenticatedGet( + Future authenticatedGet( String url, - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ) async { - var result = await _session.get( + assert(_credentials != null); + + var result = await session.get( _fillUrlWithAuthToken(url), cancellationToken, ); + if (result == null) return null; + cancellationToken?.throwIfCancelled(); if (!TimeoutExtract().isTimeoutErrorPage(result) && @@ -154,10 +150,10 @@ class DualisAuthentication { return result; } - var loginResult = await login(_username, _password, cancellationToken); + var loginResult = await login(_credentials!, cancellationToken); if (loginResult == LoginResult.LoggedIn) { - return await _session.get( + return await session.get( _fillUrlWithAuthToken(url), cancellationToken, ); @@ -167,9 +163,9 @@ class DualisAuthentication { } Future logout([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { - var logoutRequest = _session.get(dualisUrls.logoutUrl, cancellationToken); + var logoutRequest = session.get(dualisUrls.logoutUrl, cancellationToken); _session = null; _dualisUrls = null; @@ -186,9 +182,9 @@ class DualisAuthentication { void _updateAccessToken(String urlWithNewToken) { var tokenMatch = _tokenRegex.firstMatch(urlWithNewToken); - if (tokenMatch == null) return; - - _authToken = tokenMatch.group(1); + if (tokenMatch != null) { + _authToken = tokenMatch.group(1); + } } /// @@ -207,13 +203,13 @@ class DualisAuthentication { return url; } - void setLoginCredentials(String username, String password) { - _username = username; - _password = password; + void setLoginCredentials(Credentials credentials) { + _credentials = credentials; } Future loginWithPreviousCredentials( CancellationToken cancellationToken) async { - return await login(_username, _password, cancellationToken); + assert(_credentials != null); + return await login(_credentials!, cancellationToken); } } diff --git a/lib/dualis/service/dualis_scraper.dart b/lib/dualis/service/dualis_scraper.dart index 7c9c65fd..b217200b 100644 --- a/lib/dualis/service/dualis_scraper.dart +++ b/lib/dualis/service/dualis_scraper.dart @@ -1,4 +1,5 @@ import 'package:dhbwstudentapp/common/util/cancellation_token.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:dhbwstudentapp/dualis/model/study_grades.dart'; import 'package:dhbwstudentapp/dualis/service/dualis_authentication.dart'; import 'package:dhbwstudentapp/dualis/service/dualis_service.dart'; @@ -19,10 +20,10 @@ class DualisScraper { DualisUrls get _dualisUrls => _dualisAuthentication.dualisUrls; Future> loadAllModules([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { var allModulesPageResponse = await _dualisAuthentication.authenticatedGet( - _dualisUrls.studentResultsUrl, + _dualisUrls.studentResultsUrl!, cancellationToken, ); @@ -31,7 +32,7 @@ class DualisScraper { Future> loadModuleExams( String moduleDetailsUrl, [ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { var detailsResponse = await _dualisAuthentication.authenticatedGet( moduleDetailsUrl, @@ -44,10 +45,10 @@ class DualisScraper { } Future> loadSemesters([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { var courseResultsResponse = await _dualisAuthentication.authenticatedGet( - _dualisUrls.courseResultUrl, + _dualisUrls.courseResultUrl!, cancellationToken, ); @@ -65,12 +66,12 @@ class DualisScraper { return semesters; } - Future> loadSemesterModules( - String semesterName, [ - CancellationToken cancellationToken, + Future> loadSemesterModules( + String? semesterName, [ + CancellationToken? cancellationToken, ]) async { var coursePage = await _dualisAuthentication.authenticatedGet( - _dualisUrls.semesterCourseResultUrls[semesterName], + _dualisUrls.semesterCourseResultUrls[semesterName!]!, cancellationToken, ); @@ -79,10 +80,10 @@ class DualisScraper { } Future loadStudyGrades( - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ) async { var studentsResultsPage = await _dualisAuthentication.authenticatedGet( - _dualisUrls.studentResultsUrl, + _dualisUrls.studentResultsUrl!, cancellationToken, ); @@ -108,11 +109,10 @@ class DualisScraper { } Future login( - String username, - String password, - CancellationToken cancellationToken, + Credentials credentials, + CancellationToken? cancellationToken, ) { - return _dualisAuthentication.login(username, password, cancellationToken); + return _dualisAuthentication.login(credentials, cancellationToken); } Future loginWithPreviousCredentials( @@ -122,7 +122,7 @@ class DualisScraper { .loginWithPreviousCredentials(cancellationToken); } - Future logout(CancellationToken cancellationToken) { + Future logout(CancellationToken? cancellationToken) { return _dualisAuthentication.logout(cancellationToken); } @@ -130,7 +130,7 @@ class DualisScraper { return _dualisAuthentication.loginState == LoginResult.LoggedIn; } - void setLoginCredentials(String username, String password) { - _dualisAuthentication.setLoginCredentials(username, password); + void setLoginCredentials(Credentials credentials) { + _dualisAuthentication.setLoginCredentials(credentials); } } diff --git a/lib/dualis/service/dualis_service.dart b/lib/dualis/service/dualis_service.dart index ddbd93fd..a830cbf9 100644 --- a/lib/dualis/service/dualis_service.dart +++ b/lib/dualis/service/dualis_service.dart @@ -1,4 +1,5 @@ import 'package:dhbwstudentapp/common/util/cancellation_token.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:dhbwstudentapp/dualis/model/exam.dart'; import 'package:dhbwstudentapp/dualis/model/module.dart'; import 'package:dhbwstudentapp/dualis/model/semester.dart'; @@ -7,30 +8,29 @@ import 'package:dhbwstudentapp/dualis/service/dualis_scraper.dart'; abstract class DualisService { Future login( - String username, - String password, [ - CancellationToken cancellationToken, + Credentials credentials, [ + CancellationToken? cancellationToken, ]); Future queryStudyGrades([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]); Future> querySemesterNames([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]); Future> queryAllModules([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]); Future querySemester( - String name, [ - CancellationToken cancellationToken, + String? name, [ + CancellationToken? cancellationToken, ]); Future logout([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]); } @@ -48,27 +48,25 @@ class DualisServiceImpl extends DualisService { @override Future login( - String username, - String password, [ - CancellationToken cancellationToken, + Credentials credentials, [ + CancellationToken? cancellationToken, ]) async { return await _dualisScraper.login( - username, - password, + credentials, cancellationToken, ); } @override Future queryStudyGrades([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { return await _dualisScraper.loadStudyGrades(cancellationToken); } @override Future> querySemesterNames([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { var semesters = await _dualisScraper.loadSemesters(cancellationToken); @@ -83,7 +81,7 @@ class DualisServiceImpl extends DualisService { @override Future> queryAllModules([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { var dualisModules = await _dualisScraper.loadAllModules(cancellationToken); @@ -103,8 +101,8 @@ class DualisServiceImpl extends DualisService { @override Future querySemester( - String name, [ - CancellationToken cancellationToken, + String? name, [ + CancellationToken? cancellationToken, ]) async { var semesterModules = await _dualisScraper.loadSemesterModules( name, @@ -115,7 +113,7 @@ class DualisServiceImpl extends DualisService { for (var dualisModule in semesterModules) { var moduleExams = await _dualisScraper.loadModuleExams( - dualisModule.detailsUrl, + dualisModule!.detailsUrl!, cancellationToken, ); @@ -145,7 +143,7 @@ class DualisServiceImpl extends DualisService { @override Future logout([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { await _dualisScraper.logout(cancellationToken); } diff --git a/lib/dualis/service/dualis_website_model.dart b/lib/dualis/service/dualis_website_model.dart index 8e7bdf60..dc234e45 100644 --- a/lib/dualis/service/dualis_website_model.dart +++ b/lib/dualis/service/dualis_website_model.dart @@ -8,18 +8,18 @@ const String dualisEndpoint = "https://dualis.dhbw.de"; /// access token contained within the urls. /// class DualisUrls { - String courseResultUrl; - String studentResultsUrl; - String logoutUrl; - String mainPageUrl; - String monthlyScheduleUrl; + String? courseResultUrl; + String? studentResultsUrl; + late String logoutUrl; + String? mainPageUrl; + String? monthlyScheduleUrl; - Map semesterCourseResultUrls = {}; + Map semesterCourseResultUrls = {}; } class DualisSemester { final String semesterName; - final String semesterCourseResultsUrl; + final String? semesterCourseResultsUrl; final List modules; DualisSemester( @@ -30,12 +30,12 @@ class DualisSemester { } class DualisModule { - final String id; - final String name; - final String finalGrade; - final String credits; - final String detailsUrl; - final ExamState state; + final String? id; + final String? name; + final String? finalGrade; + final String? credits; + final String? detailsUrl; + final ExamState? state; DualisModule( this.id, @@ -48,11 +48,11 @@ class DualisModule { } class DualisExam { - final String tryNr; - final String moduleName; - final String name; + final String? tryNr; + final String? moduleName; + final String? name; final ExamGrade grade; - final String semester; + final String? semester; DualisExam( this.name, diff --git a/lib/dualis/service/fake_account_dualis_scraper_decorator.dart b/lib/dualis/service/fake_account_dualis_scraper_decorator.dart index 54a0e3c0..fddbbce8 100644 --- a/lib/dualis/service/fake_account_dualis_scraper_decorator.dart +++ b/lib/dualis/service/fake_account_dualis_scraper_decorator.dart @@ -1,4 +1,5 @@ import 'package:dhbwstudentapp/common/util/cancellation_token.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:dhbwstudentapp/dualis/model/study_grades.dart'; import 'package:dhbwstudentapp/dualis/service/dualis_scraper.dart'; import 'package:dhbwstudentapp/dualis/service/dualis_service.dart'; @@ -14,13 +15,12 @@ import 'package:dhbwstudentapp/schedule/model/schedule.dart'; /// area of the app. /// class FakeAccountDualisScraperDecorator implements DualisScraper { - final fakeUsername = "fakeAccount@domain.de"; - final fakePassword = "Passw0rd"; + static const _credentials = Credentials("fakeAccount@domain.de", "Passw0rd"); final DualisScraper _fakeDualisScraper = FakeDataDualisScraper(); final DualisScraper _originalDualisScraper; - DualisScraper _currentDualisScraper; + late DualisScraper _currentDualisScraper; FakeAccountDualisScraperDecorator( this._originalDualisScraper, @@ -33,14 +33,14 @@ class FakeAccountDualisScraperDecorator implements DualisScraper { @override Future> loadAllModules( - [CancellationToken cancellationToken]) { + [CancellationToken? cancellationToken]) { return _currentDualisScraper.loadAllModules(); } @override Future> loadModuleExams( String moduleDetailsUrl, [ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) { return _currentDualisScraper.loadModuleExams( moduleDetailsUrl, @@ -60,9 +60,9 @@ class FakeAccountDualisScraperDecorator implements DualisScraper { } @override - Future> loadSemesterModules( - String semesterName, [ - CancellationToken cancellationToken, + Future> loadSemesterModules( + String? semesterName, [ + CancellationToken? cancellationToken, ]) { return _currentDualisScraper.loadSemesterModules( semesterName, @@ -72,29 +72,27 @@ class FakeAccountDualisScraperDecorator implements DualisScraper { @override Future> loadSemesters( - [CancellationToken cancellationToken]) { + [CancellationToken? cancellationToken]) { return _currentDualisScraper.loadSemesters(cancellationToken); } @override - Future loadStudyGrades(CancellationToken cancellationToken) { + Future loadStudyGrades(CancellationToken? cancellationToken) { return _currentDualisScraper.loadStudyGrades(cancellationToken); } @override Future login( - String username, - String password, - CancellationToken cancellationToken, + Credentials credentials, + CancellationToken? cancellationToken, ) { - if (username == fakeUsername && password == fakePassword) { + if (credentials == _credentials) { _currentDualisScraper = _fakeDualisScraper; } else { _currentDualisScraper = _originalDualisScraper; } return _currentDualisScraper.login( - username, - password, + credentials, cancellationToken, ); } @@ -108,23 +106,20 @@ class FakeAccountDualisScraperDecorator implements DualisScraper { } @override - Future logout(CancellationToken cancellationToken) { + Future logout(CancellationToken? cancellationToken) { return _currentDualisScraper.logout( cancellationToken, ); } @override - void setLoginCredentials(String username, String password) { - if (username == fakeUsername && password == fakePassword) { + void setLoginCredentials(Credentials credentials) { + if (credentials == _credentials) { _currentDualisScraper = _fakeDualisScraper; } else { _currentDualisScraper = _originalDualisScraper; } - return _currentDualisScraper.setLoginCredentials( - username, - password, - ); + return _currentDualisScraper.setLoginCredentials(credentials); } } diff --git a/lib/dualis/service/fake_data_dualis_scraper.dart b/lib/dualis/service/fake_data_dualis_scraper.dart index fe7a90b0..56de36b8 100644 --- a/lib/dualis/service/fake_data_dualis_scraper.dart +++ b/lib/dualis/service/fake_data_dualis_scraper.dart @@ -1,4 +1,5 @@ import 'package:dhbwstudentapp/common/util/cancellation_token.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:dhbwstudentapp/dualis/model/exam.dart'; import 'package:dhbwstudentapp/dualis/model/exam_grade.dart'; import 'package:dhbwstudentapp/dualis/model/study_grades.dart'; @@ -20,7 +21,7 @@ class FakeDataDualisScraper implements DualisScraper { @override Future> loadAllModules( - [CancellationToken cancellationToken]) async { + [CancellationToken? cancellationToken]) async { await Future.delayed(Duration(milliseconds: 200)); return Future.value([ @@ -38,7 +39,7 @@ class FakeDataDualisScraper implements DualisScraper { @override Future> loadModuleExams( String moduleDetailsUrl, [ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { await Future.delayed(Duration(milliseconds: 200)); return Future.value([ @@ -63,8 +64,8 @@ class FakeDataDualisScraper implements DualisScraper { @override Future> loadSemesterModules( - String semesterName, [ - CancellationToken cancellationToken, + String? semesterName, [ + CancellationToken? cancellationToken, ]) async { await Future.delayed(Duration(milliseconds: 200)); return Future.value([ @@ -81,14 +82,14 @@ class FakeDataDualisScraper implements DualisScraper { @override Future> loadSemesters( - [CancellationToken cancellationToken]) async { + [CancellationToken? cancellationToken]) async { await Future.delayed(Duration(milliseconds: 200)); return Future.value([DualisSemester("SoSe2020", "", [])]); } @override Future loadStudyGrades( - CancellationToken cancellationToken) async { + CancellationToken? cancellationToken) async { await Future.delayed(Duration(milliseconds: 200)); return Future.value( StudyGrades( @@ -102,9 +103,8 @@ class FakeDataDualisScraper implements DualisScraper { @override Future login( - String username, - String password, - CancellationToken cancellationToken, + Credentials credentials, + CancellationToken? cancellationToken, ) async { await Future.delayed(Duration(milliseconds: 200)); _isLoggedIn = true; @@ -120,13 +120,13 @@ class FakeDataDualisScraper implements DualisScraper { } @override - Future logout(CancellationToken cancellationToken) async { + Future logout(CancellationToken? cancellationToken) async { await Future.delayed(Duration(milliseconds: 200)); _isLoggedIn = false; } @override - void setLoginCredentials(String username, String password) { + void setLoginCredentials(Credentials? credentials) { // TODO: implement setLoginCredentials } } diff --git a/lib/dualis/service/parsing/all_modules_extract.dart b/lib/dualis/service/parsing/all_modules_extract.dart index 02e76db6..cca84728 100644 --- a/lib/dualis/service/parsing/all_modules_extract.dart +++ b/lib/dualis/service/parsing/all_modules_extract.dart @@ -5,7 +5,7 @@ import 'package:html/dom.dart'; import 'package:html/parser.dart'; class AllModulesExtract { - List extractAllModules(String body) { + List extractAllModules(String? body) { try { return _extractAllModules(body); } catch (e, trace) { @@ -14,7 +14,7 @@ class AllModulesExtract { } } - List _extractAllModules(String body) { + List _extractAllModules(String? body) { var document = parse(body); var modulesTable = getElementByTagName(document, "tbody"); @@ -49,7 +49,7 @@ class AllModulesExtract { var grade = cells[4].innerHtml; var state = cells[5].children[0].attributes["alt"]; - ExamState stateEnum; + ExamState? stateEnum; if (state == "Bestanden") { stateEnum = ExamState.Passed; diff --git a/lib/dualis/service/parsing/exams_from_module_details_extract.dart b/lib/dualis/service/parsing/exams_from_module_details_extract.dart index 769c92be..d90f79e0 100644 --- a/lib/dualis/service/parsing/exams_from_module_details_extract.dart +++ b/lib/dualis/service/parsing/exams_from_module_details_extract.dart @@ -4,7 +4,7 @@ import 'package:dhbwstudentapp/dualis/service/parsing/parsing_utils.dart'; import 'package:html/parser.dart'; class ExamsFromModuleDetailsExtract { - List extractExamsFromModuleDetails(String body) { + List extractExamsFromModuleDetails(String? body) { try { return _extractExamsFromModuleDetails(body); } on ParseException catch (e, trace) { @@ -13,14 +13,14 @@ class ExamsFromModuleDetailsExtract { } } - List _extractExamsFromModuleDetails(String body) { + List _extractExamsFromModuleDetails(String? body) { var document = parse(body); var tableExams = getElementByTagName(document, "tbody"); var tableExamsRows = tableExams.getElementsByTagName("tr"); - String currentTry; - String currentModule; + String? currentTry; + String? currentModule; var exams = []; diff --git a/lib/dualis/service/parsing/login_redirect_url_extract.dart b/lib/dualis/service/parsing/login_redirect_url_extract.dart index 847edff5..ea2f2463 100644 --- a/lib/dualis/service/parsing/login_redirect_url_extract.dart +++ b/lib/dualis/service/parsing/login_redirect_url_extract.dart @@ -1,12 +1,12 @@ import 'package:html/parser.dart'; class LoginRedirectUrlExtract { - String readRedirectUrl(String body, String redirectUrl) { + String? readRedirectUrl(String? body, String redirectUrl) { var document = parse(body); var metaTags = document.getElementsByTagName("meta"); - String redirectContent; + String? redirectContent; for (var metaTag in metaTags) { if (!metaTag.attributes.containsKey("http-equiv")) continue; @@ -23,7 +23,7 @@ class LoginRedirectUrlExtract { return getUrlFromHeader(redirectContent, redirectUrl); } - String getUrlFromHeader(String refreshHeader, String endpointUrl) { + String? getUrlFromHeader(String? refreshHeader, String endpointUrl) { if (refreshHeader == null || !refreshHeader.contains("URL=")) return null; var refreshHeaderUrlIndex = refreshHeader.indexOf("URL=") + "URL=".length; diff --git a/lib/dualis/service/parsing/modules_from_course_result_page_extract.dart b/lib/dualis/service/parsing/modules_from_course_result_page_extract.dart index f903a77f..baa53cb0 100644 --- a/lib/dualis/service/parsing/modules_from_course_result_page_extract.dart +++ b/lib/dualis/service/parsing/modules_from_course_result_page_extract.dart @@ -7,8 +7,8 @@ import 'package:html/parser.dart'; class ModulesFromCourseResultPageExtract { final RegExp _extractUrlRegex = RegExp('dl_popUp\\("(.+?)"'); - List extractModulesFromCourseResultPage( - String body, + List extractModulesFromCourseResultPage( + String? body, String endpointUrl, ) { try { @@ -19,27 +19,27 @@ class ModulesFromCourseResultPageExtract { } } - List _extractModulesFromCourseResultPage( - String body, String endpointUrl) { + List _extractModulesFromCourseResultPage( + String? body, String endpointUrl) { var document = parse(body); var tableBodies = getElementByTagName(document, "tbody"); var rows = tableBodies.getElementsByTagName("tr"); - var modulesOfSemester = []; + var modulesOfSemester = []; for (var row in rows) { // Only rows with tds as child are modules if (row.children[0].localName != "td") continue; - DualisModule module = _extractModuleFromRow(row, endpointUrl); + DualisModule? module = _extractModuleFromRow(row, endpointUrl); modulesOfSemester.add(module); } return modulesOfSemester; } - DualisModule _extractModuleFromRow( + DualisModule? _extractModuleFromRow( Element row, String endpointUrl, ) { @@ -49,13 +49,13 @@ class ModulesFromCourseResultPageExtract { var name = row.children[1].innerHtml; var grade = row.children[2].innerHtml; var credits = row.children[3].innerHtml; - var status = row.children[4].innerHtml; + String? status = row.children[4].innerHtml; var detailsButton = row.children[5]; var url = _extractDetailsUrlFromButton(detailsButton, endpointUrl); status = trimAndEscapeString(status); - ExamState statusEnum; + ExamState? statusEnum; if (status == "bestanden") { statusEnum = ExamState.Passed; @@ -76,7 +76,7 @@ class ModulesFromCourseResultPageExtract { return module; } - String _extractDetailsUrlFromButton( + String? _extractDetailsUrlFromButton( Element detailsButton, String endpointUrl, ) { diff --git a/lib/dualis/service/parsing/monthly_schedule_extract.dart b/lib/dualis/service/parsing/monthly_schedule_extract.dart index d0f8e22a..019a879b 100644 --- a/lib/dualis/service/parsing/monthly_schedule_extract.dart +++ b/lib/dualis/service/parsing/monthly_schedule_extract.dart @@ -6,7 +6,7 @@ import 'package:html/parser.dart'; import 'package:intl/intl.dart'; class MonthlyScheduleExtract { - Schedule extractScheduleFromMonthly(String body) { + Schedule extractScheduleFromMonthly(String? body) { try { return _extractScheduleFromMonthly(body); } catch (e, trace) { @@ -15,7 +15,7 @@ class MonthlyScheduleExtract { } } - Schedule _extractScheduleFromMonthly(String body) { + Schedule _extractScheduleFromMonthly(String? body) { var document = parse(body); var appointments = document.getElementsByClassName("apmntLink"); @@ -29,18 +29,18 @@ class MonthlyScheduleExtract { } allEntries.sort( - (ScheduleEntry e1, ScheduleEntry e2) => e1?.start?.compareTo(e2?.start), + (ScheduleEntry e1, ScheduleEntry e2) => e1.start.compareTo(e2.start), ); return Schedule.fromList(allEntries); } ScheduleEntry _extractEntry(Element appointment) { - var date = appointment.parent.parent - .querySelector(".tbsubhead a") + var date = appointment.parent!.parent! + .querySelector(".tbsubhead a")! .attributes["title"]; - var information = appointment.attributes["title"]; + var information = appointment.attributes["title"]!; var informationParts = information.split(" / "); var startAndEnd = informationParts[0].split(" - "); diff --git a/lib/dualis/service/parsing/parsing_utils.dart b/lib/dualis/service/parsing/parsing_utils.dart index 4091c44a..e192cca9 100644 --- a/lib/dualis/service/parsing/parsing_utils.dart +++ b/lib/dualis/service/parsing/parsing_utils.dart @@ -2,7 +2,7 @@ import 'package:html/dom.dart'; import 'package:universal_html/html.dart' as html; -String trimAndEscapeString(String htmlString) { +String? trimAndEscapeString(String? htmlString) { if (htmlString == null) return null; var text = html.Element.span()..appendHtml(htmlString); @@ -45,8 +45,8 @@ Element getElementById( } class ParseException implements Exception { - Object innerException; - StackTrace trace; + Object? innerException; + StackTrace? trace; ParseException.withInner(this.innerException, this.trace); @@ -58,10 +58,10 @@ class ParseException implements Exception { class ElementNotFoundParseException implements ParseException { @override - Object innerException; + Object? innerException; @override - StackTrace trace; + StackTrace? trace; final String elementDescription; diff --git a/lib/dualis/service/parsing/semesters_from_course_result_page_extract.dart b/lib/dualis/service/parsing/semesters_from_course_result_page_extract.dart index c6ae1853..2ce424b7 100644 --- a/lib/dualis/service/parsing/semesters_from_course_result_page_extract.dart +++ b/lib/dualis/service/parsing/semesters_from_course_result_page_extract.dart @@ -5,7 +5,7 @@ import 'package:html/parser.dart'; class SemestersFromCourseResultPageExtract { List extractSemestersFromCourseResults( - String body, + String? body, String endpointUrl, ) { try { @@ -17,7 +17,7 @@ class SemestersFromCourseResultPageExtract { } List _extractSemestersFromCourseResults( - String body, String endpointUrl) { + String? body, String endpointUrl) { var page = parse(body); var semesterSelector = page.getElementById("semester"); @@ -33,10 +33,10 @@ class SemestersFromCourseResultPageExtract { var id = option.attributes["value"]; var name = option.innerHtml; - String detailsUrl; + String? detailsUrl; if (url != null) { - detailsUrl = url + id; + detailsUrl = url + id!; } semesters.add(DualisSemester( @@ -49,11 +49,11 @@ class SemestersFromCourseResultPageExtract { return semesters; } - String _extractSemesterDetailUrlPart( + String? _extractSemesterDetailUrlPart( Element semesterSelector, String endpointUrl, ) { - var dropDownSemesterSelector = semesterSelector.attributes["onchange"]; + var dropDownSemesterSelector = semesterSelector.attributes["onchange"]!; var regExp = RegExp("'([A-z0-9]*)'"); diff --git a/lib/dualis/service/parsing/study_grades_from_student_results_page_extract.dart b/lib/dualis/service/parsing/study_grades_from_student_results_page_extract.dart index 8b6ff079..3af3e401 100644 --- a/lib/dualis/service/parsing/study_grades_from_student_results_page_extract.dart +++ b/lib/dualis/service/parsing/study_grades_from_student_results_page_extract.dart @@ -4,7 +4,7 @@ import 'package:html/dom.dart'; import 'package:html/parser.dart'; class StudyGradesFromStudentResultsPageExtract { - StudyGrades extractStudyGradesFromStudentsResultsPage(String body) { + StudyGrades extractStudyGradesFromStudentsResultsPage(String? body) { try { return _extractStudyGradesFromStudentsResultsPage(body); } catch (e, trace) { @@ -13,7 +13,7 @@ class StudyGradesFromStudentResultsPageExtract { } } - StudyGrades _extractStudyGradesFromStudentsResultsPage(String body) { + StudyGrades _extractStudyGradesFromStudentsResultsPage(String? body) { var document = parse(body); var creditsTable = getElementByTagName(document, "tbody", 0); @@ -40,12 +40,12 @@ class StudyGradesFromStudentResultsPageExtract { var neededCredits = neededCreditsRow.children[0].innerHtml; // Only take the number after the colon - neededCredits = trimAndEscapeString(neededCredits.split(":")[1]); + neededCredits = trimAndEscapeString(neededCredits.split(":")[1])!; var gainedCreditsRow = rows[rows.length - 2]; var gainedCredits = trimAndEscapeString( gainedCreditsRow.children[2].innerHtml, - ); + )!; neededCredits = neededCredits.replaceAll(",", "."); gainedCredits = gainedCredits.replaceAll(",", "."); @@ -65,12 +65,12 @@ class StudyGradesFromStudentResultsPageExtract { var totalGpaRowCells = rows[0].getElementsByTagName("th"); var totalGpa = trimAndEscapeString( totalGpaRowCells[1].innerHtml, - ); + )!; var mainCoursesGpaRowCells = rows[1].getElementsByTagName("th"); var mainModulesGpa = trimAndEscapeString( mainCoursesGpaRowCells[1].innerHtml, - ); + )!; _Gpa gpa = _Gpa(); gpa.totalGpa = double.tryParse(totalGpa.replaceAll(",", ".")); @@ -81,11 +81,11 @@ class StudyGradesFromStudentResultsPageExtract { } class _Credits { - double totalCredits; - double gainedCredits; + double? totalCredits; + double? gainedCredits; } class _Gpa { - double totalGpa; - double mainModulesGpa; + double? totalGpa; + double? mainModulesGpa; } diff --git a/lib/dualis/service/parsing/urls_from_main_page_extract.dart b/lib/dualis/service/parsing/urls_from_main_page_extract.dart index 1cea8a03..d8638001 100644 --- a/lib/dualis/service/parsing/urls_from_main_page_extract.dart +++ b/lib/dualis/service/parsing/urls_from_main_page_extract.dart @@ -4,7 +4,7 @@ import 'package:html/parser.dart'; class UrlsFromMainPageExtract { void parseMainPage( - String body, + String? body, DualisUrls dualsUrls, String endpointUrl, ) { @@ -17,7 +17,7 @@ class UrlsFromMainPageExtract { } void _parseMainPage( - String body, + String? body, DualisUrls dualisUrls, String endpointUrl, ) { @@ -29,14 +29,14 @@ class UrlsFromMainPageExtract { var logoutElement = getElementById(document, "logoutButton"); dualisUrls.courseResultUrl = - endpointUrl + courseResultsElement.attributes['href']; + endpointUrl + courseResultsElement.attributes['href']!; dualisUrls.studentResultsUrl = - endpointUrl + studentResultsElement.attributes['href']; + endpointUrl + studentResultsElement.attributes['href']!; dualisUrls.monthlyScheduleUrl = - endpointUrl + monthlyScheduleElement.attributes["href"]; + endpointUrl + monthlyScheduleElement.attributes["href"]!; - dualisUrls.logoutUrl = endpointUrl + logoutElement.attributes['href']; + dualisUrls.logoutUrl = endpointUrl + logoutElement.attributes['href']!; } } diff --git a/lib/dualis/service/session.dart b/lib/dualis/service/session.dart index a1563f8d..01d14088 100644 --- a/lib/dualis/service/session.dart +++ b/lib/dualis/service/session.dart @@ -5,19 +5,22 @@ import 'package:dhbwstudentapp/schedule/service/schedule_source.dart'; import 'package:http/http.dart'; import 'package:http_client_helper/http_client_helper.dart' as http; +// TODO: [Leptopoda] requestCancellationToken and Cancellation token cleanup +// TODO: [Leptopoda] Pass Uri objects and not strings + /// /// Handles cookies and provides a session. Execute your api calls with the /// provided get and set methods. /// class Session { - Map cookies = {}; + Map _cookies = {}; /// /// Execute a GET request and return the result body as string /// - Future get( + Future get( String url, [ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { var response = await rawGet(url, cancellationToken); @@ -32,9 +35,9 @@ class Session { } } - Future rawGet( + Future rawGet( String url, [ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { if (cancellationToken == null) cancellationToken = CancellationToken(); @@ -50,13 +53,13 @@ class Session { var response = await http.HttpClientHelper.get( requestUri, cancelToken: requestCancellationToken, - headers: cookies, + headers: _cookies, ); if (response == null && !requestCancellationToken.isCanceled) throw ServiceRequestFailed("Http request failed!"); - _updateCookie(response); + _updateCookie(response!); return response; } on http.OperationCanceledError catch (_) { @@ -73,10 +76,10 @@ class Session { /// /// Execute a POST request and return the result body as string /// - Future post( + Future post( String url, - dynamic data, [ - CancellationToken cancellationToken, + Map data, [ + CancellationToken? cancellationToken, ]) async { var response = await rawPost(url, data, cancellationToken); @@ -91,10 +94,10 @@ class Session { } } - Future rawPost( + Future rawPost( String url, - dynamic data, [ - CancellationToken cancellationToken, + Map data, [ + CancellationToken? cancellationToken, ]) async { if (cancellationToken == null) cancellationToken = CancellationToken(); var requestCancellationToken = http.CancellationToken(); @@ -107,14 +110,14 @@ class Session { var response = await http.HttpClientHelper.post( Uri.parse(url), body: data, - headers: cookies, + headers: _cookies, cancelToken: requestCancellationToken, ); if (response == null && !requestCancellationToken.isCanceled) throw ServiceRequestFailed("Http request failed!"); - _updateCookie(response); + _updateCookie(response!); return response; } on http.OperationCanceledError catch (_) { @@ -129,7 +132,7 @@ class Session { } void _updateCookie(Response response) { - String rawCookie = response.headers['set-cookie']; + String? rawCookie = response.headers['set-cookie']; if (rawCookie != null) { int index = rawCookie.indexOf(';'); @@ -137,7 +140,7 @@ class Session { cookie = cookie.replaceAll(" ", ""); - cookies['cookie'] = cookie; + _cookies['cookie'] = cookie; } } } diff --git a/lib/dualis/ui/dualis_navigation_entry.dart b/lib/dualis/ui/dualis_navigation_entry.dart index 95e2097f..3fc5eea4 100644 --- a/lib/dualis/ui/dualis_navigation_entry.dart +++ b/lib/dualis/ui/dualis_navigation_entry.dart @@ -1,6 +1,5 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; import 'package:dhbwstudentapp/common/ui/custom_icons_icons.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/dualis/ui/dualis_page.dart'; import 'package:dhbwstudentapp/dualis/ui/viewmodels/study_grades_view_model.dart'; import 'package:dhbwstudentapp/dualis/ui/widgets/dualis_help_dialog.dart'; @@ -9,13 +8,9 @@ import 'package:flutter/material.dart'; import 'package:kiwi/kiwi.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; -class DualisNavigationEntry extends NavigationEntry { - StudyGradesViewModel _viewModel; - +class DualisNavigationEntry extends NavigationEntry { @override - Widget icon(BuildContext context) { - return Icon(Icons.data_usage); - } + Icon icon = Icon(Icons.data_usage); @override String title(BuildContext context) { @@ -23,15 +18,11 @@ class DualisNavigationEntry extends NavigationEntry { } @override - BaseViewModel initViewModel() { - if (_viewModel == null) { - _viewModel = StudyGradesViewModel( - KiwiContainer().resolve(), - KiwiContainer().resolve(), - ); - } - - return _viewModel; + StudyGradesViewModel initViewModel() { + return StudyGradesViewModel( + KiwiContainer().resolve(), + KiwiContainer().resolve(), + ); } @override @@ -41,27 +32,27 @@ class DualisNavigationEntry extends NavigationEntry { @override List appBarActions(BuildContext context) { - initViewModel(); return [ PropertyChangeProvider( - value: _viewModel, + value: model, child: PropertyChangeConsumer( - builder: (BuildContext _, StudyGradesViewModel __, Set ___) => - _viewModel.loginState != LoginState.LoggedIn - ? IconButton( - icon: Icon(Icons.help_outline), - onPressed: () async { - await DualisHelpDialog().show(context); - }, - tooltip: L.of(context).helpButtonTooltip, - ) - : IconButton( - icon: const Icon(CustomIcons.logout), - onPressed: () async { - await _viewModel.logout(); - }, - tooltip: L.of(context).logoutButtonTooltip, - ), + builder: + (BuildContext _, StudyGradesViewModel? __, Set? ___) => + model.loginState != LoginState.LoggedIn + ? IconButton( + icon: Icon(Icons.help_outline), + onPressed: () async { + await DualisHelpDialog().show(context); + }, + tooltip: L.of(context).helpButtonTooltip, + ) + : IconButton( + icon: const Icon(CustomIcons.logout), + onPressed: () async { + await model.logout(); + }, + tooltip: L.of(context).logoutButtonTooltip, + ), ), ), ]; diff --git a/lib/dualis/ui/dualis_page.dart b/lib/dualis/ui/dualis_page.dart index 5f866d55..cd9e017c 100644 --- a/lib/dualis/ui/dualis_page.dart +++ b/lib/dualis/ui/dualis_page.dart @@ -1,5 +1,4 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/dualis/ui/exam_results_page/exam_results_page.dart'; import 'package:dhbwstudentapp/dualis/ui/login/dualis_login_page.dart'; import 'package:dhbwstudentapp/dualis/ui/study_overview/study_overview_page.dart'; @@ -12,7 +11,7 @@ import 'package:provider/provider.dart'; class DualisPage extends StatelessWidget { @override Widget build(BuildContext context) { - StudyGradesViewModel viewModel = Provider.of(context); + StudyGradesViewModel viewModel = Provider.of(context); Widget widget; diff --git a/lib/dualis/ui/exam_results_page/exam_results_page.dart b/lib/dualis/ui/exam_results_page/exam_results_page.dart index 315dc141..25f09e4f 100644 --- a/lib/dualis/ui/exam_results_page/exam_results_page.dart +++ b/lib/dualis/ui/exam_results_page/exam_results_page.dart @@ -38,14 +38,14 @@ class ExamResultsPage extends StatelessWidget { ], builder: ( BuildContext context, - StudyGradesViewModel model, - Set properties, + StudyGradesViewModel? model, + Set? properties, ) => DropdownButton( - onChanged: (value) => model.loadSemester(value), - value: model.currentSemesterName, - items: (model.allSemesterNames ?? []) - .map( + onChanged: model?.loadSemester, + value: model?.currentSemesterName, + items: model?.allSemesterNames + ?.map( (n) => DropdownMenuItem( child: Text(n), value: n, @@ -62,10 +62,10 @@ class ExamResultsPage extends StatelessWidget { properties: const ["currentSemester"], builder: ( BuildContext context, - StudyGradesViewModel model, - Set properties, + StudyGradesViewModel? model, + Set? properties, ) => - model.currentSemester != null + model!.currentSemester != null ? buildModulesColumn(context, model) : const Padding( padding: EdgeInsets.fromLTRB(0, 16, 0, 0), @@ -81,7 +81,7 @@ class ExamResultsPage extends StatelessWidget { Widget buildModulesColumn( BuildContext context, StudyGradesViewModel viewModel) { return AnimatedSwitcher( - layoutBuilder: (Widget currentChild, List previousChildren) { + layoutBuilder: (Widget? currentChild, List previousChildren) { List children = previousChildren; if (currentChild != null) children = children.toList()..add(currentChild); @@ -103,7 +103,7 @@ class ExamResultsPage extends StatelessWidget { var dataTables = []; var isFirstModule = true; - for (var module in viewModel.currentSemester.modules) { + for (var module in viewModel.currentSemester!.modules) { dataTables.add(DataTable( horizontalMargin: 24, columnSpacing: 0, @@ -157,14 +157,12 @@ class ExamResultsPage extends StatelessWidget { case ExamGradeState.NotGraded: return Text(""); case ExamGradeState.Graded: - return Text(grade.gradeValue); + return Text(grade.gradeValue!); case ExamGradeState.Passed: return Text(L.of(context).examPassed); case ExamGradeState.Failed: return Text(L.of(context).examNotPassed); } - - return Text(""); } List buildModuleColumns(BuildContext context, var module, diff --git a/lib/dualis/ui/login/dualis_login_page.dart b/lib/dualis/ui/login/dualis_login_page.dart index 204dd8a2..3fd8ff86 100644 --- a/lib/dualis/ui/login/dualis_login_page.dart +++ b/lib/dualis/ui/login/dualis_login_page.dart @@ -1,5 +1,4 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/dualis/ui/viewmodels/study_grades_view_model.dart'; import 'package:dhbwstudentapp/dualis/ui/widgets/login_form_widget.dart'; import 'package:flutter/material.dart'; @@ -8,7 +7,7 @@ import 'package:provider/provider.dart'; class DualisLoginPage extends StatelessWidget { @override Widget build(BuildContext context) { - StudyGradesViewModel viewModel = Provider.of(context); + StudyGradesViewModel viewModel = Provider.of(context); return buildLoginPage(context, viewModel); } diff --git a/lib/dualis/ui/study_overview/study_overview_page.dart b/lib/dualis/ui/study_overview/study_overview_page.dart index 1cd0f267..fd6a8a4c 100644 --- a/lib/dualis/ui/study_overview/study_overview_page.dart +++ b/lib/dualis/ui/study_overview/study_overview_page.dart @@ -39,10 +39,10 @@ class StudyOverviewPage extends StatelessWidget { properties: const ["studyGrades"], builder: ( BuildContext context, - StudyGradesViewModel model, - Set properties, + StudyGradesViewModel? model, + Set? properties, ) => - model.studyGrades != null + model!.studyGrades != null ? Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -54,7 +54,7 @@ class StudyOverviewPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.baseline, children: [ Text( - model.studyGrades.gpaTotal.toString(), + model.studyGrades!.gpaTotal.toString(), style: Theme.of(context).textTheme.headline3, ), Padding( @@ -72,7 +72,7 @@ class StudyOverviewPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.baseline, children: [ Text( - model.studyGrades.gpaMainModules.toString(), + model.studyGrades!.gpaMainModules.toString(), style: Theme.of(context).textTheme.headline3, ), Padding( @@ -90,7 +90,7 @@ class StudyOverviewPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.baseline, children: [ Text( - "${model.studyGrades.creditsGained} / ${model.studyGrades.creditsTotal}", + "${model.studyGrades!.creditsGained} / ${model.studyGrades!.creditsTotal}", style: Theme.of(context).textTheme.headline3, ), Padding( @@ -129,10 +129,10 @@ class StudyOverviewPage extends StatelessWidget { properties: const ["allModules"], builder: ( BuildContext context, - StudyGradesViewModel model, - Set properties, + StudyGradesViewModel? model, + Set? properties, ) => - model.allModules != null + model!.allModules != null ? buildModulesDataTable(context, model) : buildProgressIndicator(), ), @@ -154,13 +154,13 @@ class StudyOverviewPage extends StatelessWidget { ) { var dataRows = []; - for (var module in model.allModules) { + for (var module in model.allModules!) { dataRows.add( DataRow( cells: [ - DataCell(Text(module.name)), - DataCell(Text(module.credits)), - DataCell(Text(module.grade)), + DataCell(Text(module.name!)), + DataCell(Text(module.credits!)), + DataCell(Text(module.grade!)), DataCell(GradeStateIcon(state: module.state)), ], ), diff --git a/lib/dualis/ui/viewmodels/study_grades_view_model.dart b/lib/dualis/ui/viewmodels/study_grades_view_model.dart index 2b7305b1..32251a4f 100644 --- a/lib/dualis/ui/viewmodels/study_grades_view_model.dart +++ b/lib/dualis/ui/viewmodels/study_grades_view_model.dart @@ -25,26 +25,26 @@ class StudyGradesViewModel extends BaseViewModel { LoginState _loginState = LoginState.LoggedOut; LoginState get loginState => _loginState; - StudyGrades _studyGrades; - StudyGrades get studyGrades => _studyGrades; + StudyGrades? _studyGrades; + StudyGrades? get studyGrades => _studyGrades; final CancelableMutex _studyGradesCancellationToken = CancelableMutex(); - List _allModules; - List get allModules => _allModules; + List? _allModules; + List? get allModules => _allModules; final CancelableMutex _allModulesCancellationToken = CancelableMutex(); - List _semesterNames; - List get allSemesterNames => _semesterNames; + List? _semesterNames; + List? get allSemesterNames => _semesterNames; final CancelableMutex _semesterNamesCancellationToken = CancelableMutex(); - Semester _currentSemester; - Semester get currentSemester => _currentSemester; + Semester? _currentSemester; + Semester? get currentSemester => _currentSemester; final CancelableMutex _currentSemesterCancellationToken = CancelableMutex(); - String _currentSemesterName; - String get currentSemesterName => _currentSemesterName; + String? _currentSemesterName; + String? get currentSemesterName => _currentSemesterName; - String _currentLoadingSemesterName; + String? _currentLoadingSemesterName; StudyGradesViewModel(this._preferencesProvider, this._dualisService); @@ -54,10 +54,7 @@ class StudyGradesViewModel extends BaseViewModel { bool success; try { - var result = await _dualisService.login( - credentials.username, - credentials.password, - ); + var result = await _dualisService.login(credentials); success = result == LoginResult.LoggedIn; } on OperationCancelledException catch (_) { @@ -95,7 +92,7 @@ class StudyGradesViewModel extends BaseViewModel { await _preferencesProvider.clearDualisCredentials(); } - Future loadCredentials() async { + Future loadCredentials() async { return await _preferencesProvider.loadDualisCredentials(); } @@ -109,7 +106,7 @@ class StudyGradesViewModel extends BaseViewModel { } Future loadStudyGrades() async { - if (_studyGrades != null) return Future.value(); + if (_studyGrades != null) return; print("Loading study grades..."); @@ -130,7 +127,7 @@ class StudyGradesViewModel extends BaseViewModel { } Future loadAllModules() async { - if (_allModules != null) return Future.value(); + if (_allModules != null) return; print("Loading all modules..."); @@ -151,7 +148,7 @@ class StudyGradesViewModel extends BaseViewModel { notifyListeners("allModules"); } - Future loadSemester(String semesterName) async { + Future loadSemester(String? semesterName) async { if (_currentSemester != null && _currentSemesterName == semesterName) return Future.value(); @@ -186,7 +183,7 @@ class StudyGradesViewModel extends BaseViewModel { } Future loadSemesterNames() async { - if (_semesterNames != null) return Future.value(); + if (_semesterNames != null) return; print("Loading semester names..."); @@ -211,14 +208,14 @@ class StudyGradesViewModel extends BaseViewModel { Future _loadInitialSemester() async { if (_semesterNames == null) return; - if (_semesterNames.isEmpty) return; + if (_semesterNames!.isEmpty) return; var lastViewedSemester = await _preferencesProvider.getLastViewedSemester(); - if (_semesterNames.contains(lastViewedSemester)) { + if (_semesterNames!.contains(lastViewedSemester)) { loadSemester(lastViewedSemester); } else { - loadSemester(_semesterNames.first); + loadSemester(_semesterNames!.first); } } diff --git a/lib/dualis/ui/widgets/grade_state_icon.dart b/lib/dualis/ui/widgets/grade_state_icon.dart index 8c3e1236..5984b6e4 100644 --- a/lib/dualis/ui/widgets/grade_state_icon.dart +++ b/lib/dualis/ui/widgets/grade_state_icon.dart @@ -2,11 +2,11 @@ import 'package:dhbwstudentapp/dualis/model/exam.dart'; import 'package:flutter/material.dart'; class GradeStateIcon extends StatelessWidget { - final ExamState state; + final ExamState? state; const GradeStateIcon({ - Key key, - this.state, + Key? key, + required this.state, }) : super(key: key); @override @@ -17,9 +17,8 @@ class GradeStateIcon extends StatelessWidget { case ExamState.Failed: return const Icon(Icons.close, color: Colors.red); case ExamState.Pending: + default: return Container(); } - - return Container(); } } diff --git a/lib/dualis/ui/widgets/login_form_widget.dart b/lib/dualis/ui/widgets/login_form_widget.dart index 2e71236e..7d061a64 100644 --- a/lib/dualis/ui/widgets/login_form_widget.dart +++ b/lib/dualis/ui/widgets/login_form_widget.dart @@ -3,7 +3,7 @@ import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:flutter/material.dart'; typedef OnLogin = Future Function(Credentials credentials); -typedef OnLoadCredentials = Future Function(); +typedef OnLoadCredentials = Future Function(); typedef OnSaveCredentials = Future Function(Credentials credentials); typedef OnClearCredentials = Future Function(); typedef GetDoSaveCredentials = Future Function(); @@ -19,77 +19,52 @@ class LoginForm extends StatefulWidget { final String loginFailedText; const LoginForm({ - Key key, - this.onLogin, - this.title, - this.loginFailedText, - this.onLoadCredentials, - this.onSaveCredentials, - this.onClearCredentials, - this.getDoSaveCredentials, + Key? key, + required this.onLogin, + required this.title, + required this.loginFailedText, + required this.onLoadCredentials, + required this.onSaveCredentials, + required this.onClearCredentials, + required this.getDoSaveCredentials, }) : super(key: key); @override - _LoginFormState createState() => _LoginFormState( - onLogin, - title, - loginFailedText, - onLoadCredentials, - onSaveCredentials, - onClearCredentials, - getDoSaveCredentials, - ); + _LoginFormState createState() => _LoginFormState(); } class _LoginFormState extends State { - final OnLogin _onLogin; - final OnLoadCredentials _onLoadCredentials; - final OnSaveCredentials _onSaveCredentials; - final OnClearCredentials _onClearCredentials; - final GetDoSaveCredentials _getDoSaveCredentials; - final Widget _title; - - final String _loginFailedText; - bool _storeCredentials = false; bool _loginFailed = false; bool _isLoading = false; - final TextEditingController _usernameEditingController = - TextEditingController(); - final TextEditingController _passwordEditingController = - TextEditingController(); - - _LoginFormState( - this._onLogin, - this._title, - this._loginFailedText, - this._onLoadCredentials, - this._onSaveCredentials, - this._onClearCredentials, - this._getDoSaveCredentials, - ); + final CredentialsEditingController _controller = + CredentialsEditingController(); @override void initState() { super.initState(); - if (_onLoadCredentials != null && _getDoSaveCredentials != null) { - _getDoSaveCredentials().then((value) { - setState(() { - _storeCredentials = value; - }); - - _onLoadCredentials().then((value) { - _usernameEditingController.text = value.username; - _passwordEditingController.text = value.password; + widget.getDoSaveCredentials().then((value) { + setState(() { + _storeCredentials = value; + }); + widget.onLoadCredentials().then((credentials) { + if (credentials != null) { + _controller.credentials = credentials; if (mounted) { setState(() {}); } - }); + } }); - } + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); } @override @@ -98,14 +73,12 @@ class _LoginFormState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - _title != null - ? Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 24), - child: _title, - ) - : Container(), + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 24), + child: widget.title, + ), TextField( - controller: _usernameEditingController, + controller: _controller.username, decoration: InputDecoration( enabled: !_isLoading, hintText: L.of(context).loginUsername, @@ -113,13 +86,13 @@ class _LoginFormState extends State { ), ), TextField( - controller: _passwordEditingController, + controller: _controller.password, obscureText: true, decoration: InputDecoration( enabled: !_isLoading, hintText: L.of(context).loginPassword, icon: Icon(Icons.lock_outline), - errorText: _loginFailed ? _loginFailedText : null, + errorText: _loginFailed ? widget.loginFailedText : null, ), ), Padding( @@ -130,7 +103,8 @@ class _LoginFormState extends State { title: Text( L.of(context).dualisStoreCredentials, ), - onChanged: (bool value) { + onChanged: (bool? value) { + if (value == null) return; setState(() { _storeCredentials = value; }); @@ -165,19 +139,16 @@ class _LoginFormState extends State { _isLoading = true; }); - if (!_storeCredentials && _onClearCredentials != null) { - await _onClearCredentials(); + if (!_storeCredentials) { + await widget.onClearCredentials(); } - var credentials = Credentials( - _usernameEditingController.text, - _passwordEditingController.text, - ); + var credentials = _controller.credentials; - bool loginSuccess = await _onLogin(credentials); + bool loginSuccess = await widget.onLogin(credentials); - if (loginSuccess && _storeCredentials && _onSaveCredentials != null) { - await _onSaveCredentials(credentials); + if (loginSuccess && _storeCredentials) { + await widget.onSaveCredentials(credentials); } setState(() { diff --git a/lib/information/ui/useful_information_navigation_entry.dart b/lib/information/ui/useful_information_navigation_entry.dart index 14271bf5..08713249 100644 --- a/lib/information/ui/useful_information_navigation_entry.dart +++ b/lib/information/ui/useful_information_navigation_entry.dart @@ -1,18 +1,17 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; +import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/information/ui/usefulinformation/useful_information_page.dart'; import 'package:dhbwstudentapp/ui/navigation/navigation_entry.dart'; import 'package:flutter/material.dart'; -class UsefulInformationNavigationEntry extends NavigationEntry { +class UsefulInformationNavigationEntry extends NavigationEntry { @override Widget build(BuildContext context) { return UsefulInformationPage(); } @override - Widget icon(BuildContext context) { - return Icon(Icons.info_outline); - } + Icon icon = Icon(Icons.info_outline); @override String title(BuildContext context) { @@ -21,4 +20,10 @@ class UsefulInformationNavigationEntry extends NavigationEntry { @override String get route => "usefulInformation"; + + @override + BaseViewModel initViewModel() => BaseViewModel(); + + @override + List appBarActions(BuildContext context) => []; } diff --git a/lib/information/ui/usefulinformation/useful_information_page.dart b/lib/information/ui/usefulinformation/useful_information_page.dart index 903db7af..d480dd32 100644 --- a/lib/information/ui/usefulinformation/useful_information_page.dart +++ b/lib/information/ui/usefulinformation/useful_information_page.dart @@ -26,7 +26,8 @@ class UsefulInformationPage extends StatelessWidget { leading: Icon(Icons.email), title: Text(L.of(context).informationPageRoundcube), onTap: () { - openLink("https://lehre-webmail.dhbw-stuttgart.de/roundcubemail/"); + openLink( + "https://lehre-webmail.dhbw-stuttgart.de/roundcubemail/"); }, ), ListTile( diff --git a/lib/native/widget/android_widget_helper.dart b/lib/native/widget/android_widget_helper.dart index 464d69de..a193aac1 100644 --- a/lib/native/widget/android_widget_helper.dart +++ b/lib/native/widget/android_widget_helper.dart @@ -30,7 +30,7 @@ class AndroidWidgetHelper implements WidgetHelper { } @override - Future areWidgetsSupported() async { + Future areWidgetsSupported() async { try { return await platform.invokeMethod('areWidgetsSupported'); } on Exception catch (_) { diff --git a/lib/native/widget/ios_widget_helper.dart b/lib/native/widget/ios_widget_helper.dart index 7e4a2b2e..0aa61fb8 100644 --- a/lib/native/widget/ios_widget_helper.dart +++ b/lib/native/widget/ios_widget_helper.dart @@ -32,7 +32,7 @@ class IOSWidgetHelper implements WidgetHelper { } @override - Future areWidgetsSupported() async { + Future areWidgetsSupported() async { try { return await platform.invokeMethod('areWidgetsSupported'); } on PlatformException catch (_) { diff --git a/lib/native/widget/widget_helper.dart b/lib/native/widget/widget_helper.dart index 8b8e98ad..e0f03028 100644 --- a/lib/native/widget/widget_helper.dart +++ b/lib/native/widget/widget_helper.dart @@ -8,7 +8,7 @@ import 'package:dhbwstudentapp/native/widget/ios_widget_helper.dart'; /// methods to enable/disable or update the widget /// class WidgetHelper { - static WidgetHelper _instance; + static WidgetHelper? _instance; WidgetHelper() { if (_instance != null) return; @@ -27,14 +27,14 @@ class WidgetHelper { /// scheduled and will happen soon. /// Future requestWidgetRefresh() { - return _instance.requestWidgetRefresh(); + return _instance!.requestWidgetRefresh(); } /// /// Checks if widgets are supported by the device /// - Future areWidgetsSupported() { - return _instance.areWidgetsSupported(); + Future areWidgetsSupported() { + return _instance!.areWidgetsSupported(); } /// @@ -42,7 +42,7 @@ class WidgetHelper { /// its full functionality. /// Future enableWidget() { - return _instance.enableWidget(); + return _instance!.enableWidget(); } /// @@ -50,7 +50,7 @@ class WidgetHelper { /// only provide placeholder content or limited functionality. /// Future disableWidget() { - return _instance.disableWidget(); + return _instance!.disableWidget(); } } diff --git a/lib/schedule/background/background_schedule_update.dart b/lib/schedule/background/background_schedule_update.dart index 7c63d1a6..c8106b8b 100644 --- a/lib/schedule/background/background_schedule_update.dart +++ b/lib/schedule/background/background_schedule_update.dart @@ -22,8 +22,8 @@ class BackgroundScheduleUpdate extends TaskCallback { return; } - var today = toDayOfWeek(toStartOfDay(DateTime.now()), DateTime.monday); - var end = addDays(today, 7 * 3); + var today = toDayOfWeek(toStartOfDay(DateTime.now()), DateTime.monday)!; + var end = addDays(today, 7 * 3)!; var cancellationToken = CancellationToken(); diff --git a/lib/schedule/background/calendar_synchronizer.dart b/lib/schedule/background/calendar_synchronizer.dart index 158f89d2..f67ef0b4 100644 --- a/lib/schedule/background/calendar_synchronizer.dart +++ b/lib/schedule/background/calendar_synchronizer.dart @@ -35,7 +35,7 @@ class CalendarSynchronizer { listDateEntries; if (await preferencesProvider.isCalendarSyncEnabled()) { - Calendar selectedCalendar = + Calendar? selectedCalendar = await preferencesProvider.getSelectedCalendar(); if (selectedCalendar == null) return; CalendarAccess().addOrUpdateDates(listDateEntries, selectedCalendar); @@ -47,7 +47,6 @@ class CalendarSynchronizer { Future.delayed(Duration(seconds: 10), () { if (!scheduleSourceProvider.didSetupCorrectly()) return; scheduleProvider.getUpdatedSchedule( - DateTime.now(), DateTime.now().add(Duration(days: 30)), CancellationToken(), diff --git a/lib/schedule/business/schedule_diff_calculator.dart b/lib/schedule/business/schedule_diff_calculator.dart index 077bdc17..c8be9661 100644 --- a/lib/schedule/business/schedule_diff_calculator.dart +++ b/lib/schedule/business/schedule_diff_calculator.dart @@ -39,17 +39,17 @@ class ScheduleDiffCalculator { return entry1.start.isAtSameMomentAs(entry2.start) && entry1.end.isAtSameMomentAs(entry2.end) && entry1.type == entry2.type && - (entry1.room ?? "") == (entry2.room ?? "") && - (entry1.details ?? "") == (entry2.details ?? "") && - (entry1.title ?? "") == (entry2.title ?? "") && - (entry1.professor ?? "") == (entry2.professor ?? ""); + entry1.room == entry2.room && + entry1.details == entry2.details && + entry1.title == entry2.title && + entry1.professor == entry2.professor; } ScheduleDiff _tryConnectNewAndOldEntries( List addedEntries, List removedEntries, ) { - var allDistinctTitles = []; + var allDistinctTitles = []; allDistinctTitles.addAll(addedEntries.map((ScheduleEntry e) => e.title)); allDistinctTitles.addAll(removedEntries.map((ScheduleEntry e) => e.title)); @@ -166,7 +166,10 @@ class ScheduleDiff { final List removedEntries; final List updatedEntries; - ScheduleDiff({this.addedEntries, this.removedEntries, this.updatedEntries}); + ScheduleDiff( + {required this.addedEntries, + required this.removedEntries, + required this.updatedEntries}); bool didSomethingChange() { return addedEntries.isNotEmpty || diff --git a/lib/schedule/business/schedule_provider.dart b/lib/schedule/business/schedule_provider.dart index 69d7b426..0dee19a6 100644 --- a/lib/schedule/business/schedule_provider.dart +++ b/lib/schedule/business/schedule_provider.dart @@ -33,7 +33,7 @@ class ScheduleProvider { final List _scheduleUpdatedCallbacks = []; - ScheduleFilter _scheduleFilter; + late ScheduleFilter _scheduleFilter; final List _scheduleEntryChangedCallbacks = []; @@ -73,7 +73,7 @@ class ScheduleProvider { var updatedSchedule = await _scheduleSource.currentScheduleSource .querySchedule(start, end, cancellationToken); - var schedule = updatedSchedule.schedule; + var schedule = updatedSchedule?.schedule; if (schedule == null) { print("No schedule returned!"); @@ -97,10 +97,10 @@ class ScheduleProvider { } for (var c in _scheduleUpdatedCallbacks) { - await c(schedule, start, end); + await c(schedule!, start, end); } - updatedSchedule = ScheduleQueryResult(schedule, updatedSchedule.errors); + updatedSchedule = ScheduleQueryResult(schedule!, updatedSchedule!.errors); return updatedSchedule; } on ScheduleQueryFailedException catch (e, trace) { @@ -162,7 +162,7 @@ class ScheduleProvider { for (var addedEntry in diff.addedEntries) { if (queryInformation.any((i) => - addedEntry.end.isAfter(i.start) && + addedEntry.end.isAfter(i!.start) && addedEntry.start.isBefore(i.end))) { cleanedAddedEntries.add(addedEntry); } diff --git a/lib/schedule/business/schedule_source_provider.dart b/lib/schedule/business/schedule_source_provider.dart index 06e244b9..4b0a1ff4 100644 --- a/lib/schedule/business/schedule_source_provider.dart +++ b/lib/schedule/business/schedule_source_provider.dart @@ -55,7 +55,7 @@ class ScheduleSourceProvider { }; if (initializer.containsKey(scheduleSourceType)) { - scheduleSource = await initializer[scheduleSourceType](); + scheduleSource = await initializer[scheduleSourceType]!(); } _currentScheduleSource = scheduleSource; @@ -72,11 +72,7 @@ class ScheduleSourceProvider { Future _getScheduleSourceType() async { var type = await _preferencesProvider.getScheduleSourceType(); - var scheduleSourceType = type != null - ? ScheduleSourceType.values[type] - : ScheduleSourceType.None; - - return scheduleSourceType; + return ScheduleSourceType.values[type]; } Future _dualisScheduleSource() async { @@ -84,7 +80,7 @@ class ScheduleSourceProvider { var credentials = await _preferencesProvider.loadDualisCredentials(); - if (credentials.allFieldsFilled()) { + if (credentials != null) { dualis.setLoginCredentials(credentials); return ErrorReportScheduleSourceDecorator(dualis); } else { @@ -130,7 +126,9 @@ class ScheduleSourceProvider { return InvalidScheduleSource(); } - Future setupForRapla(String url) async { + Future setupForRapla(String? url) async { + if (url == null) return; + await _preferencesProvider.setRaplaUrl(url); await _preferencesProvider .setScheduleSourceType(ScheduleSourceType.Rapla.index); @@ -157,7 +155,9 @@ class ScheduleSourceProvider { ); } - Future setupForIcal(String url) async { + Future setupForIcal(String? url) async { + if (url == null) return; + await _preferencesProvider.setIcalUrl(url); await _preferencesProvider .setScheduleSourceType(ScheduleSourceType.Ical.index); @@ -171,8 +171,8 @@ class ScheduleSourceProvider { ); } - Future setupForMannheim(Course selectedCourse) async { - if(selectedCourse == null) return; + Future setupForMannheim(Course? selectedCourse) async { + if (selectedCourse == null) return; await _preferencesProvider.setMannheimScheduleId(selectedCourse.scheduleId); await _preferencesProvider.setIcalUrl(selectedCourse.icalUrl); await _preferencesProvider @@ -188,8 +188,7 @@ class ScheduleSourceProvider { } bool didSetupCorrectly() { - return _currentScheduleSource != null && - !(_currentScheduleSource is InvalidScheduleSource); + return !(_currentScheduleSource is InvalidScheduleSource); } void addDidChangeScheduleSourceCallback(OnDidChangeScheduleSource callback) { diff --git a/lib/schedule/data/schedule_entry_entity.dart b/lib/schedule/data/schedule_entry_entity.dart index 2f532d8f..8a5ae1bc 100644 --- a/lib/schedule/data/schedule_entry_entity.dart +++ b/lib/schedule/data/schedule_entry_entity.dart @@ -2,7 +2,7 @@ import 'package:dhbwstudentapp/common/data/database_entity.dart'; import 'package:dhbwstudentapp/schedule/model/schedule_entry.dart'; class ScheduleEntryEntity extends DatabaseEntity { - ScheduleEntry _scheduleEntry; + ScheduleEntry? _scheduleEntry; ScheduleEntryEntity.fromModel(ScheduleEntry scheduleEntry) { _scheduleEntry = scheduleEntry; @@ -14,15 +14,9 @@ class ScheduleEntryEntity extends DatabaseEntity { @override void fromMap(Map map) { - DateTime startDate; - if (map["start"] != null) { - startDate = DateTime.fromMillisecondsSinceEpoch(map["start"]); - } + var startDate = DateTime.fromMillisecondsSinceEpoch(map["start"]); - DateTime endDate; - if (map["end"] != null) { - endDate = DateTime.fromMillisecondsSinceEpoch(map["end"]); - } + var endDate = DateTime.fromMillisecondsSinceEpoch(map["end"]); _scheduleEntry = ScheduleEntry( id: map["id"], @@ -38,18 +32,18 @@ class ScheduleEntryEntity extends DatabaseEntity { @override Map toMap() { return { - "id": _scheduleEntry.id, - "start": _scheduleEntry.start?.millisecondsSinceEpoch ?? 0, - "end": _scheduleEntry.end?.millisecondsSinceEpoch ?? 0, - "details": _scheduleEntry.details ?? "", - "professor": _scheduleEntry.professor ?? "", - "room": _scheduleEntry.room ?? "", - "title": _scheduleEntry.title ?? "", - "type": _scheduleEntry.type?.index + "id": _scheduleEntry!.id, + "start": _scheduleEntry!.start.millisecondsSinceEpoch, + "end": _scheduleEntry!.end.millisecondsSinceEpoch, + "details": _scheduleEntry!.details, + "professor": _scheduleEntry!.professor, + "room": _scheduleEntry!.room, + "title": _scheduleEntry!.title, + "type": _scheduleEntry!.type.index }; } - ScheduleEntry asScheduleEntry() => _scheduleEntry; + ScheduleEntry asScheduleEntry() => _scheduleEntry!; static String tableName() => "ScheduleEntries"; } diff --git a/lib/schedule/data/schedule_entry_repository.dart b/lib/schedule/data/schedule_entry_repository.dart index 0f72149f..e46ed5db 100644 --- a/lib/schedule/data/schedule_entry_repository.dart +++ b/lib/schedule/data/schedule_entry_repository.dart @@ -34,7 +34,7 @@ class ScheduleEntryRepository { return schedule; } - Future queryExistingScheduleEntry(ScheduleEntry entry) async { + Future queryExistingScheduleEntry(ScheduleEntry entry) async { var rows = await _database.queryRows( ScheduleEntryEntity.tableName(), where: "start=? AND end=? AND title=? AND details=? AND professor=?", @@ -52,7 +52,7 @@ class ScheduleEntryRepository { return ScheduleEntryEntity.fromMap(rows[0]).asScheduleEntry(); } - Future queryNextScheduleEntry(DateTime dateTime) async { + Future queryNextScheduleEntry(DateTime dateTime) async { var nextScheduleEntry = await _database.queryRows( ScheduleEntryEntity.tableName(), where: "start>?", @@ -70,18 +70,16 @@ class ScheduleEntryRepository { return null; } - Future> queryAllNamesOfScheduleEntries() async { + Future> queryAllNamesOfScheduleEntries() async { var allNames = await _database.rawQuery( "SELECT DISTINCT title FROM ScheduleEntries", [], ); - return allNames.map((e) => e["title"] as String).toList(); + return allNames.map((e) => e["title"] as String?).toList(); } Future saveScheduleEntry(ScheduleEntry entry) async { - var row = ScheduleEntryEntity.fromModel(entry).toMap(); - var existingEntry = await queryExistingScheduleEntry(entry); if (existingEntry != null) { @@ -89,6 +87,7 @@ class ScheduleEntryRepository { return; } + var row = ScheduleEntryEntity.fromModel(entry).toMap(); if (entry.id == null) { var id = await _database.insert(ScheduleEntryEntity.tableName(), row); entry.id = id; @@ -98,7 +97,7 @@ class ScheduleEntryRepository { } Future saveSchedule(Schedule schedule) async { - for (var entry in schedule.entries ?? []) { + for (var entry in schedule.entries) { saveScheduleEntry(entry); } } diff --git a/lib/schedule/data/schedule_filter_repository.dart b/lib/schedule/data/schedule_filter_repository.dart index 39e41480..651bc270 100644 --- a/lib/schedule/data/schedule_filter_repository.dart +++ b/lib/schedule/data/schedule_filter_repository.dart @@ -5,14 +5,14 @@ class ScheduleFilterRepository { ScheduleFilterRepository(this._database); - Future> queryAllHiddenNames() async { + Future> queryAllHiddenNames() async { var rows = await _database.queryRows("ScheduleEntryFilters"); - var names = rows.map((e) => e['title'] as String).toList(); + var names = rows.map((e) => e['title'] as String?).toList(); return names; } - Future saveAllHiddenNames(List hiddenNames) async { + Future saveAllHiddenNames(List hiddenNames) async { await _database.deleteWhere("ScheduleEntryFilters"); for (var name in hiddenNames) { diff --git a/lib/schedule/data/schedule_query_information_entity.dart b/lib/schedule/data/schedule_query_information_entity.dart index abd0d5e2..b8bc4e1d 100644 --- a/lib/schedule/data/schedule_query_information_entity.dart +++ b/lib/schedule/data/schedule_query_information_entity.dart @@ -2,7 +2,7 @@ import 'package:dhbwstudentapp/common/data/database_entity.dart'; import 'package:dhbwstudentapp/schedule/model/schedule_query_information.dart'; class ScheduleQueryInformationEntity extends DatabaseEntity { - ScheduleQueryInformation _scheduleQueryInformation; + ScheduleQueryInformation? _scheduleQueryInformation; ScheduleQueryInformationEntity.fromModel( ScheduleQueryInformation scheduleQueryInformation, @@ -16,18 +16,18 @@ class ScheduleQueryInformationEntity extends DatabaseEntity { @override void fromMap(Map map) { - DateTime startDate; + DateTime? startDate; if (map["start"] != null) { startDate = DateTime.fromMillisecondsSinceEpoch(map["start"]); } - DateTime endDate; + DateTime? endDate; if (map["end"] != null) { endDate = DateTime.fromMillisecondsSinceEpoch(map["end"]); } - DateTime queryTimeDate; - if (map["end"] != null) { + DateTime? queryTimeDate; + if (map["queryTime"] != null) { queryTimeDate = DateTime.fromMillisecondsSinceEpoch(map["queryTime"]); } @@ -38,13 +38,13 @@ class ScheduleQueryInformationEntity extends DatabaseEntity { @override Map toMap() { return { - "start": _scheduleQueryInformation.start?.millisecondsSinceEpoch ?? 0, - "end": _scheduleQueryInformation.end?.millisecondsSinceEpoch ?? 0, - "queryTime": _scheduleQueryInformation.queryTime?.millisecondsSinceEpoch, + "start": _scheduleQueryInformation!.start.millisecondsSinceEpoch, + "end": _scheduleQueryInformation!.end.millisecondsSinceEpoch, + "queryTime": _scheduleQueryInformation!.queryTime?.millisecondsSinceEpoch, }; } - ScheduleQueryInformation asScheduleQueryInformation() => + ScheduleQueryInformation? asScheduleQueryInformation() => _scheduleQueryInformation; static String tableName() => "ScheduleQueryInformation"; diff --git a/lib/schedule/data/schedule_query_information_repository.dart b/lib/schedule/data/schedule_query_information_repository.dart index 669d300e..e43d8fcf 100644 --- a/lib/schedule/data/schedule_query_information_repository.dart +++ b/lib/schedule/data/schedule_query_information_repository.dart @@ -7,7 +7,7 @@ class ScheduleQueryInformationRepository { ScheduleQueryInformationRepository(this._database); - Future getOldestQueryTimeBetweenDates( + Future getOldestQueryTimeBetweenDates( DateTime start, DateTime end) async { var oldestQueryTimeDate = await _database.queryAggregator( "SELECT MIN(queryTime) FROM ScheduleQueryInformation WHERE start<=? AND end>=?", @@ -17,10 +17,12 @@ class ScheduleQueryInformationRepository { ], ); + if (oldestQueryTimeDate == null) return null; + return DateTime.fromMillisecondsSinceEpoch(oldestQueryTimeDate); } - Future> getQueryInformationBetweenDates( + Future> getQueryInformationBetweenDates( DateTime start, DateTime end) async { var rows = await _database.queryRows( ScheduleQueryInformationEntity.tableName(), @@ -31,7 +33,7 @@ class ScheduleQueryInformationRepository { ], ); - var scheduleQueryInformation = []; + var scheduleQueryInformation = []; for (var row in rows) { scheduleQueryInformation.add( diff --git a/lib/schedule/model/schedule.dart b/lib/schedule/model/schedule.dart index 7a948471..3d4aeac4 100644 --- a/lib/schedule/model/schedule.dart +++ b/lib/schedule/model/schedule.dart @@ -25,11 +25,11 @@ class Schedule { } } - Schedule trim(DateTime startDate, DateTime endDate) { + Schedule trim(DateTime? startDate, DateTime? endDate) { var newList = []; for (var entry in entries) { - if (startDate.isBefore(entry.end) && endDate.isAfter(entry.start)) { + if (startDate!.isBefore(entry.end) && endDate!.isAfter(entry.start)) { newList.add(entry); } } @@ -40,28 +40,28 @@ class Schedule { return schedule; } - DateTime getStartDate() { + DateTime? getStartDate() { if (entries.isEmpty) return null; - var date = entries?.reduce((ScheduleEntry a, ScheduleEntry b) { - return a.start.isBefore(b.start) ? a : b; - })?.start; + var date = entries.reduce((ScheduleEntry? a, ScheduleEntry? b) { + return a!.start.isBefore(b!.start) ? a : b; + }).start; return date; } - DateTime getEndDate() { + DateTime? getEndDate() { if (entries.isEmpty) return null; - var date = entries?.reduce((ScheduleEntry a, ScheduleEntry b) { - return a.end.isAfter(b.end) ? a : b; - })?.end; + var date = entries.reduce((ScheduleEntry? a, ScheduleEntry? b) { + return a!.end.isAfter(b!.end) ? a : b; + }).end; return date; } - DateTime getStartTime() { - DateTime earliestTime; + DateTime? getStartTime() { + DateTime? earliestTime; for (var entry in entries) { var entryTime = DateTime( @@ -83,8 +83,8 @@ class Schedule { return earliestTime; } - DateTime getEndTime() { - DateTime latestTime; + DateTime? getEndTime() { + DateTime? latestTime; for (var entry in entries) { var entryTime = DateTime( @@ -106,7 +106,7 @@ class Schedule { return latestTime; } - Schedule copyWith({List entries}) { + Schedule copyWith({required List entries}) { var schedule = Schedule.fromList(entries); schedule.urls.addAll(urls); diff --git a/lib/schedule/model/schedule_entry.dart b/lib/schedule/model/schedule_entry.dart index 619df572..d925d5b0 100644 --- a/lib/schedule/model/schedule_entry.dart +++ b/lib/schedule/model/schedule_entry.dart @@ -7,7 +7,7 @@ enum ScheduleEntryType { } class ScheduleEntry { - int id; + int? id; final DateTime start; final DateTime end; final String title; @@ -18,14 +18,19 @@ class ScheduleEntry { ScheduleEntry({ this.id, - this.start, - this.end, - this.title, - this.details, - this.professor, - this.room, - this.type, - }); + DateTime? start, + DateTime? end, + String? title, + String? details, + String? professor, + String? room, + required this.type, + }) : this.start = start ?? DateTime.fromMicrosecondsSinceEpoch(0), + this.end = end ?? DateTime.fromMicrosecondsSinceEpoch(0), + this.details = details ?? "", + this.professor = professor ?? "", + this.room = room ?? "", + this.title = title ?? ""; bool equalsWithIdIgnored(ScheduleEntry other) { return this.start == other.start && @@ -40,7 +45,7 @@ class ScheduleEntry { List getDifferentProperties(ScheduleEntry entry) { var changedProperties = []; - if ((title ?? "") != (entry.title ?? "")) { + if (title != entry.title) { changedProperties.add("title"); } if (start != entry.start) { @@ -49,13 +54,13 @@ class ScheduleEntry { if (end != entry.end) { changedProperties.add("end"); } - if ((details ?? "") != (entry.details ?? "")) { + if (details != entry.details) { changedProperties.add("details"); } - if ((professor ?? "") != (entry.professor ?? "")) { + if (professor != entry.professor) { changedProperties.add("professor"); } - if ((room ?? "") != (entry.room ?? "")) { + if (room != entry.room) { changedProperties.add("room"); } if (type != entry.type) { @@ -65,14 +70,15 @@ class ScheduleEntry { return changedProperties; } + // TODO: [Leptopoda] use buildrunner ScheduleEntry copyWith( - {DateTime start, - DateTime end, - String title, - String details, - String professor, - String room, - ScheduleEntryType type}) { + {DateTime? start, + DateTime? end, + String? title, + String? details, + String? professor, + String? room, + ScheduleEntryType? type}) { return ScheduleEntry( id: id, start: start ?? this.start, diff --git a/lib/schedule/model/schedule_query_information.dart b/lib/schedule/model/schedule_query_information.dart index e527f01a..b9e124b8 100644 --- a/lib/schedule/model/schedule_query_information.dart +++ b/lib/schedule/model/schedule_query_information.dart @@ -1,7 +1,12 @@ class ScheduleQueryInformation { final DateTime start; final DateTime end; - final DateTime queryTime; + final DateTime? queryTime; - ScheduleQueryInformation(this.start, this.end, this.queryTime); + ScheduleQueryInformation( + DateTime? start, + DateTime? end, + this.queryTime, + ) : this.start = start ?? DateTime.fromMillisecondsSinceEpoch(0), + this.end = end ?? DateTime.fromMillisecondsSinceEpoch(0); } diff --git a/lib/schedule/model/schedule_query_result.dart b/lib/schedule/model/schedule_query_result.dart index 26382398..421807bc 100644 --- a/lib/schedule/model/schedule_query_result.dart +++ b/lib/schedule/model/schedule_query_result.dart @@ -4,16 +4,16 @@ class ScheduleQueryResult { final Schedule schedule; final List errors; - bool get hasError => errors?.isNotEmpty ?? false; + bool get hasError => errors.isNotEmpty; ScheduleQueryResult(this.schedule, this.errors); } class ParseError { final String object; - final String trace; + final String? trace; - ParseError(Object object, StackTrace trace) - : object = object?.toString(), + ParseError(Object object, [StackTrace? trace]) + : object = object.toString(), trace = trace?.toString(); } diff --git a/lib/schedule/service/dualis/dualis_schedule_source.dart b/lib/schedule/service/dualis/dualis_schedule_source.dart index f515f501..9863689f 100644 --- a/lib/schedule/service/dualis/dualis_schedule_source.dart +++ b/lib/schedule/service/dualis/dualis_schedule_source.dart @@ -13,11 +13,11 @@ class DualisScheduleSource extends ScheduleSource { DualisScheduleSource(this._dualisScraper); @override - Future querySchedule(DateTime from, DateTime to, - [CancellationToken cancellationToken]) async { + Future querySchedule(DateTime? from, DateTime? to, + [CancellationToken? cancellationToken]) async { if (cancellationToken == null) cancellationToken = CancellationToken(); - DateTime current = toStartOfMonth(from); + DateTime current = toStartOfMonth(from)!; var schedule = Schedule(); var allErrors = []; @@ -25,12 +25,12 @@ class DualisScheduleSource extends ScheduleSource { if (!_dualisScraper.isLoggedIn()) await _dualisScraper.loginWithPreviousCredentials(cancellationToken); - while (to.isAfter(current) && !cancellationToken.isCancelled()) { + while (to!.isAfter(current) && !cancellationToken.isCancelled()) { try { var monthSchedule = await _dualisScraper.loadMonthlySchedule( current, cancellationToken); - if (monthSchedule != null) schedule.merge(monthSchedule); + schedule.merge(monthSchedule); } on OperationCancelledException { rethrow; } on ParseException catch (ex, trace) { @@ -51,10 +51,7 @@ class DualisScheduleSource extends ScheduleSource { } Future setLoginCredentials(Credentials credentials) async { - _dualisScraper.setLoginCredentials( - credentials.username, - credentials.password, - ); + _dualisScraper.setLoginCredentials(credentials); } @override diff --git a/lib/schedule/service/error_report_schedule_source_decorator.dart b/lib/schedule/service/error_report_schedule_source_decorator.dart index 15c5e88b..d1930b98 100644 --- a/lib/schedule/service/error_report_schedule_source_decorator.dart +++ b/lib/schedule/service/error_report_schedule_source_decorator.dart @@ -9,8 +9,8 @@ class ErrorReportScheduleSourceDecorator extends ScheduleSource { ErrorReportScheduleSourceDecorator(this._scheduleSource); @override - Future querySchedule(DateTime from, DateTime to, - [CancellationToken cancellationToken]) async { + Future querySchedule(DateTime? from, DateTime? to, + [CancellationToken? cancellationToken]) async { try { var schedule = await _scheduleSource.querySchedule( from, diff --git a/lib/schedule/service/ical/ical_parser.dart b/lib/schedule/service/ical/ical_parser.dart index 60435489..5042a245 100644 --- a/lib/schedule/service/ical/ical_parser.dart +++ b/lib/schedule/service/ical/ical_parser.dart @@ -34,7 +34,7 @@ class IcalParser { List entries = []; for (var match in matches) { - var entry = _parseEntry(match.group(1)); + var entry = _parseEntry(match.group(1)!); entries.add(entry); } @@ -50,7 +50,7 @@ class IcalParser { unicode: true, ).allMatches(entryData); - Map properties = {}; + Map properties = {}; for (var property in allProperties) { properties[property.group(1)] = property.group(3); @@ -67,23 +67,23 @@ class IcalParser { ); } - DateTime _parseDate(String date) { + DateTime? _parseDate(String? date) { + if (date == null) return null; + var match = RegExp( dateTimeRegex, unicode: true, - ).firstMatch(date ?? ""); + ).firstMatch(date); - if (match == null) { - return null; - } + if (match == null) return null; return DateTime( - int.tryParse(match.group(1)), - int.tryParse(match.group(2)), - int.tryParse(match.group(3)), - int.tryParse(match.group(4)), - int.tryParse(match.group(5)), - int.tryParse(match.group(6)), + int.tryParse(match.group(1)!)!, + int.tryParse(match.group(2)!)!, + int.tryParse(match.group(3)!)!, + int.tryParse(match.group(4)!)!, + int.tryParse(match.group(5)!)!, + int.tryParse(match.group(6)!)!, ); } } diff --git a/lib/schedule/service/ical/ical_schedule_source.dart b/lib/schedule/service/ical/ical_schedule_source.dart index 495ada39..2bf3112a 100644 --- a/lib/schedule/service/ical/ical_schedule_source.dart +++ b/lib/schedule/service/ical/ical_schedule_source.dart @@ -10,9 +10,9 @@ import 'package:http_client_helper/http_client_helper.dart' as http; class IcalScheduleSource extends ScheduleSource { final IcalParser _icalParser = IcalParser(); - String _url; + String? _url; - void setIcalUrl(String url) { + void setIcalUrl(String? url) { _url = url; } @@ -22,12 +22,12 @@ class IcalScheduleSource extends ScheduleSource { } @override - Future querySchedule( - DateTime from, - DateTime to, [ - CancellationToken cancellationToken, + Future querySchedule( + DateTime? from, + DateTime? to, [ + CancellationToken? cancellationToken, ]) async { - var response = await _makeRequest(_url, cancellationToken); + var response = await _makeRequest(_url!, cancellationToken!); if (response == null) return null; try { @@ -45,7 +45,7 @@ class IcalScheduleSource extends ScheduleSource { } } - Future _makeRequest( + Future _makeRequest( String url, CancellationToken cancellationToken) async { url = url.replaceAll("webcal://", "https://"); @@ -76,8 +76,9 @@ class IcalScheduleSource extends ScheduleSource { return null; } - static bool isValidUrl(String url) { + static bool isValidUrl(String? url) { try { + if (url == null) return false; Uri.parse(url); } catch (e) { return false; diff --git a/lib/schedule/service/invalid_schedule_source.dart b/lib/schedule/service/invalid_schedule_source.dart index 0dcd0030..7f55936b 100644 --- a/lib/schedule/service/invalid_schedule_source.dart +++ b/lib/schedule/service/invalid_schedule_source.dart @@ -5,9 +5,9 @@ import 'package:dhbwstudentapp/schedule/service/schedule_source.dart'; class InvalidScheduleSource extends ScheduleSource { @override Future querySchedule( - DateTime from, - DateTime to, [ - CancellationToken cancellationToken, + DateTime? from, + DateTime? to, [ + CancellationToken? cancellationToken, ]) { throw StateError("Schedule source not properly configured"); } diff --git a/lib/schedule/service/isolate_schedule_source_decorator.dart b/lib/schedule/service/isolate_schedule_source_decorator.dart index 02ea3a96..5071aed6 100644 --- a/lib/schedule/service/isolate_schedule_source_decorator.dart +++ b/lib/schedule/service/isolate_schedule_source_decorator.dart @@ -11,35 +11,35 @@ import 'package:dhbwstudentapp/schedule/service/schedule_source.dart'; class IsolateScheduleSourceDecorator extends ScheduleSource { final ScheduleSource _scheduleSource; - Stream _isolateToMain; - Isolate _isolate; - SendPort _sendPort; + Stream? _isolateToMain; + Isolate? _isolate; + SendPort? _sendPort; IsolateScheduleSourceDecorator(this._scheduleSource); @override - Future querySchedule(DateTime from, DateTime to, - [CancellationToken cancellationToken]) async { + Future querySchedule(DateTime? from, DateTime? to, + [CancellationToken? cancellationToken]) async { await _initializeIsolate(); // Use the cancellation token to send a cancel message. // The isolate then uses a new instance to cancel the request - cancellationToken.setCancellationCallback(() { - _sendPort.send({"type": "cancel"}); + cancellationToken!.setCancellationCallback(() { + _sendPort!.send({"type": "cancel"}); }); - _sendPort.send({ + _sendPort!.send({ "type": "execute", "source": _scheduleSource, "from": from, "to": to, }); - final completer = Completer(); + final completer = Completer(); - ScheduleQueryFailedException potentialException; + ScheduleQueryFailedException? potentialException; - final subscription = _isolateToMain.listen((result) { + final subscription = _isolateToMain!.listen((result) { cancellationToken.setCancellationCallback(null); if (result != null && !(result is ScheduleQueryResult)) { @@ -55,7 +55,7 @@ class IsolateScheduleSourceDecorator extends ScheduleSource { subscription.cancel(); if (potentialException != null) { - throw potentialException; + throw potentialException!; } return result; @@ -70,7 +70,7 @@ class IsolateScheduleSourceDecorator extends ScheduleSource { _isolateToMain = isolateToMain.asBroadcastStream(); _isolate = await Isolate.spawn( scheduleSourceIsolateEntryPoint, isolateToMain.sendPort); - _sendPort = await _isolateToMain.first; + _sendPort = await _isolateToMain!.first as SendPort?; } @override @@ -84,7 +84,7 @@ void scheduleSourceIsolateEntryPoint(SendPort sendPort) async { var port = ReceivePort(); sendPort.send(port.sendPort); - CancellationToken token; + CancellationToken? token; await for (var message in port) { if (message["type"] == "execute") { @@ -99,12 +99,12 @@ void scheduleSourceIsolateEntryPoint(SendPort sendPort) async { Future executeQueryScheduleMessage( Map map, SendPort sendPort, - CancellationToken token, + CancellationToken? token, ) async { try { ScheduleSource source = map["source"]; - DateTime from = map["from"]; - DateTime to = map["to"]; + DateTime? from = map["from"]; + DateTime? to = map["to"]; var result = await source.querySchedule(from, to, token); diff --git a/lib/schedule/service/mannheim/mannheim_course_response_parser.dart b/lib/schedule/service/mannheim/mannheim_course_response_parser.dart index 0cb543f0..9912082b 100644 --- a/lib/schedule/service/mannheim/mannheim_course_response_parser.dart +++ b/lib/schedule/service/mannheim/mannheim_course_response_parser.dart @@ -14,7 +14,7 @@ class MannheimCourseResponseParser { for (var e in options) { var label = e.attributes["label"] ?? ""; var value = e.attributes["value"] ?? ""; - var title = e.parent.attributes["label"] ?? ""; + var title = e.parent!.attributes["label"] ?? ""; if (label == "" || value == "") continue; if (label.trim() == "Kurs auswählen") continue; diff --git a/lib/schedule/service/mannheim/mannheim_course_scraper.dart b/lib/schedule/service/mannheim/mannheim_course_scraper.dart index 7ca27aa0..ecd3b732 100644 --- a/lib/schedule/service/mannheim/mannheim_course_scraper.dart +++ b/lib/schedule/service/mannheim/mannheim_course_scraper.dart @@ -15,7 +15,7 @@ class Course { class MannheimCourseScraper { Future> loadCourses([ - CancellationToken cancellationToken, + CancellationToken? cancellationToken, ]) async { if (cancellationToken == null) cancellationToken = CancellationToken(); @@ -42,15 +42,14 @@ class MannheimCourseScraper { if (response == null && !requestCancellationToken.isCanceled) throw ServiceRequestFailed("Http request failed!"); - return response; + return response!; } on http.OperationCanceledError catch (_) { throw OperationCancelledException(); } catch (ex) { if (!requestCancellationToken.isCanceled) rethrow; + throw ServiceRequestFailed("Http request failed!"); } finally { cancellationToken.setCancellationCallback(null); } - - return null; } } diff --git a/lib/schedule/service/rapla/rapla_monthly_response_parser.dart b/lib/schedule/service/rapla/rapla_monthly_response_parser.dart index 5886f610..2a2f6bd3 100644 --- a/lib/schedule/service/rapla/rapla_monthly_response_parser.dart +++ b/lib/schedule/service/rapla/rapla_monthly_response_parser.dart @@ -12,7 +12,7 @@ class RaplaMonthlyResponseParser { static ScheduleQueryResult parseMonthlyTable( Element monthTable, ) { - var title = monthTable.parent.getElementsByClassName("title"); + var title = monthTable.parent!.getElementsByClassName("title"); var monthAndYear = title[0].text; var yearString = monthAndYear.split(" ")[1]; @@ -37,7 +37,7 @@ class RaplaMonthlyResponseParser { try { var entry = RaplaParsingUtils.extractScheduleEntryOrThrow( dayEntry, - DateTime(year, month, day), + DateTime(year!, month!, day!), ); allEntries.add(entry); @@ -50,7 +50,7 @@ class RaplaMonthlyResponseParser { return ScheduleQueryResult(Schedule.fromList(allEntries), parseErrors); } - static int _monthStringToDateTime(String monthAndYear) { + static int? _monthStringToDateTime(String monthAndYear) { var monthString = monthAndYear.split(" ")[0]; var monthNames = { "Januar": DateTime.january, diff --git a/lib/schedule/service/rapla/rapla_parsing_utils.dart b/lib/schedule/service/rapla/rapla_parsing_utils.dart index 90cc3aec..51830201 100644 --- a/lib/schedule/service/rapla/rapla_parsing_utils.dart +++ b/lib/schedule/service/rapla/rapla_parsing_utils.dart @@ -46,7 +46,7 @@ class RaplaParsingUtils { if (start == null || end == null) throw ElementNotFoundParseException("start and end date container"); - ScheduleEntry scheduleEntry; + ScheduleEntry? scheduleEntry; // The important information is stored in a html element called tooltip. // Depending on the Rapla configuration the tooltip is available or not. @@ -66,12 +66,12 @@ class RaplaParsingUtils { } static ScheduleEntry improveScheduleEntry(ScheduleEntry scheduleEntry) { - if (scheduleEntry.title == null) { + if (scheduleEntry.title == "") { throw ElementNotFoundParseException("title"); } var professor = scheduleEntry.professor; - if (professor?.endsWith(",") ?? false) { + if (professor.endsWith(",")) { scheduleEntry = scheduleEntry.copyWith( professor: professor.substring(0, professor.length - 1)); } @@ -87,7 +87,7 @@ class RaplaParsingUtils { static ScheduleEntry extractScheduleFromTooltip( List tooltip, Element value, - ScheduleEntry scheduleEntry, + ScheduleEntry? scheduleEntry, DateTime start, DateTime end) { var infotable = tooltip[0].getElementsByClassName(INFOTABLE_CLASS); @@ -120,7 +120,7 @@ class RaplaParsingUtils { static ScheduleEntry extractScheduleDetailsFromCell( List timeAndClassName, - ScheduleEntry scheduleEntry, + ScheduleEntry? scheduleEntry, DateTime start, DateTime end) { var descriptionHtml = timeAndClassName[0].innerHtml.substring(12); @@ -156,12 +156,11 @@ class RaplaParsingUtils { var typeString = strongTag[0].innerHtml; - var type = ScheduleEntryType.Unknown; if (entryTypeMapping.containsKey(typeString)) { - type = entryTypeMapping[typeString]; + return entryTypeMapping[typeString]!; + } else { + return ScheduleEntryType.Unknown; } - - return type; } static Map _parsePropertiesTable(Element infotable) { @@ -175,7 +174,7 @@ class RaplaParsingUtils { return map; } - static DateTime _parseTime(String timeString, DateTime date) { + static DateTime? _parseTime(String timeString, DateTime date) { try { var time = DateFormat("HH:mm").parse(timeString.substring(0, 5)); return DateTime(date.year, date.month, date.day, time.hour, time.minute); @@ -200,7 +199,7 @@ class RaplaParsingUtils { // selected year in the date selector var comboBoxes = document.getElementsByTagName("select"); - String year; + String? year; for (var box in comboBoxes) { if (box.attributes.containsKey("name") && box.attributes["name"] == "year") { diff --git a/lib/schedule/service/rapla/rapla_response_parser.dart b/lib/schedule/service/rapla/rapla_response_parser.dart index f0a7e353..cc37efff 100644 --- a/lib/schedule/service/rapla/rapla_response_parser.dart +++ b/lib/schedule/service/rapla/rapla_response_parser.dart @@ -27,7 +27,7 @@ class RaplaResponseParser { } return ScheduleQueryResult(Schedule(), [ - ParseError("Did not find a week_table and month_table class", null), + ParseError("Did not find a week_table and month_table class"), ]); } } diff --git a/lib/schedule/service/rapla/rapla_schedule_source.dart b/lib/schedule/service/rapla/rapla_schedule_source.dart index fac25cf4..02082dbe 100644 --- a/lib/schedule/service/rapla/rapla_schedule_source.dart +++ b/lib/schedule/service/rapla/rapla_schedule_source.dart @@ -11,7 +11,7 @@ import 'package:http_client_helper/http_client_helper.dart' as http; class RaplaScheduleSource extends ScheduleSource { final RaplaResponseParser responseParser = RaplaResponseParser(); - String raplaUrl; + String? raplaUrl; RaplaScheduleSource({this.raplaUrl}); @@ -20,9 +20,9 @@ class RaplaScheduleSource extends ScheduleSource { } @override - Future querySchedule(DateTime from, DateTime to, - [CancellationToken cancellationToken]) async { - DateTime current = toDayOfWeek(from, DateTime.monday); + Future querySchedule(DateTime? from, DateTime? to, + [CancellationToken? cancellationToken]) async { + DateTime current = toDayOfWeek(from, DateTime.monday)!; if (cancellationToken == null) cancellationToken = CancellationToken(); @@ -31,16 +31,18 @@ class RaplaScheduleSource extends ScheduleSource { var didChangeMonth = false; - while ((to.isAfter(current) && !cancellationToken.isCancelled()) || + while ((to!.isAfter(current) && !cancellationToken.isCancelled()) || didChangeMonth) { try { var weekSchedule = await _fetchRaplaSource(current, cancellationToken); - if (weekSchedule.schedule != null) { - schedule.merge(weekSchedule.schedule); + if (weekSchedule?.schedule != null) { + schedule.merge(weekSchedule!.schedule); } - allErrors.addAll(weekSchedule.errors ?? []); + if (weekSchedule != null) { + allErrors.addAll(weekSchedule.errors); + } } on OperationCancelledException { rethrow; } on ParseException catch (ex, trace) { @@ -50,7 +52,7 @@ class RaplaScheduleSource extends ScheduleSource { } var currentMonth = current.month; - current = toNextWeek(current); + current = toNextWeek(current)!; var nextMonth = current.month; // Some rapla instances only return the dates in the current month. @@ -67,7 +69,7 @@ class RaplaScheduleSource extends ScheduleSource { return ScheduleQueryResult(schedule, allErrors); } - Future _fetchRaplaSource( + Future _fetchRaplaSource( DateTime date, CancellationToken cancellationToken, ) async { @@ -79,9 +81,7 @@ class RaplaScheduleSource extends ScheduleSource { try { var schedule = responseParser.parseSchedule(response.body); - if (schedule?.schedule?.urls != null) { - schedule.schedule.urls.add(requestUri.toString()); - } + schedule.schedule.urls.add(requestUri.toString()); return schedule; } on ParseException catch (_) { @@ -100,11 +100,11 @@ class RaplaScheduleSource extends ScheduleSource { /// - ?user=XXXXXXXXXX&file=XXXXX&page=XXXXXX /// Uri _buildRequestUri(DateTime date) { - if (!raplaUrl.startsWith("http://") && !raplaUrl.startsWith("https://")) { + if (!raplaUrl!.startsWith("http://") && !raplaUrl!.startsWith("https://")) { raplaUrl = "http://$raplaUrl"; } - var uri = Uri.parse(raplaUrl); + var uri = Uri.parse(raplaUrl!); bool hasKeyParameter = uri.queryParameters.containsKey("key"); bool hasUserParameter = uri.queryParameters.containsKey("user"); @@ -114,7 +114,7 @@ class RaplaScheduleSource extends ScheduleSource { bool hasAllocatableId = uri.queryParameters.containsKey("allocatable_id"); bool hasSalt = uri.queryParameters.containsKey("salt"); - Map parameters = {}; + Map parameters = {}; if (hasKeyParameter) { parameters["key"] = uri.queryParameters["key"]; @@ -133,14 +133,14 @@ class RaplaScheduleSource extends ScheduleSource { parameters["month"] = date.month.toString(); parameters["year"] = date.year.toString(); - if (raplaUrl.startsWith("https")) { + if (raplaUrl!.startsWith("https")) { return Uri.https(uri.authority, uri.path, parameters); } else { return Uri.http(uri.authority, uri.path, parameters); } } - Future _makeRequest( + Future _makeRequest( Uri uri, CancellationToken cancellationToken) async { var requestCancellationToken = http.CancellationToken(); @@ -169,7 +169,7 @@ class RaplaScheduleSource extends ScheduleSource { @override bool canQuery() { - return isValidUrl(raplaUrl); + return isValidUrl(raplaUrl!); } static bool isValidUrl(String url) { @@ -181,26 +181,24 @@ class RaplaScheduleSource extends ScheduleSource { return false; } - if (uri != null) { - bool hasKeyParameter = uri.queryParameters.containsKey("key"); - bool hasUserParameter = uri.queryParameters.containsKey("user"); - bool hasFileParameter = uri.queryParameters.containsKey("file"); - bool hasPageParameter = uri.queryParameters.containsKey("page"); + bool hasKeyParameter = uri.queryParameters.containsKey("key"); + bool hasUserParameter = uri.queryParameters.containsKey("user"); + bool hasFileParameter = uri.queryParameters.containsKey("file"); + bool hasPageParameter = uri.queryParameters.containsKey("page"); - bool hasAllocatableId = uri.queryParameters.containsKey("allocatable_id"); - bool hasSalt = uri.queryParameters.containsKey("salt"); + bool hasAllocatableId = uri.queryParameters.containsKey("allocatable_id"); + bool hasSalt = uri.queryParameters.containsKey("salt"); - if (hasUserParameter && hasFileParameter && hasPageParameter) { - return true; - } + if (hasUserParameter && hasFileParameter && hasPageParameter) { + return true; + } - if (hasSalt && hasAllocatableId && hasKeyParameter) { - return true; - } + if (hasSalt && hasAllocatableId && hasKeyParameter) { + return true; + } - if (hasKeyParameter) { - return true; - } + if (hasKeyParameter) { + return true; } return false; @@ -215,9 +213,9 @@ enum FailureReason { class ScheduleOrFailure { final FailureReason reason; - final Schedule schedule; - final Object exception; - final StackTrace trace; + final Schedule? schedule; + final Object? exception; + final StackTrace? trace; bool get success => reason == FailureReason.Success; diff --git a/lib/schedule/service/rapla/rapla_several_months_response_parser.dart b/lib/schedule/service/rapla/rapla_several_months_response_parser.dart index dad3b018..96bab905 100644 --- a/lib/schedule/service/rapla/rapla_several_months_response_parser.dart +++ b/lib/schedule/service/rapla/rapla_several_months_response_parser.dart @@ -9,14 +9,11 @@ class RaplaSeveralMonthsResponseParser { Document document, List monthTables, ) { - var parseErrors = []; var allEntries = []; - for(var monthTable in monthTables) { - var result = RaplaMonthlyResponseParser.parseMonthlyTable( - monthTable - ); + for (var monthTable in monthTables) { + var result = RaplaMonthlyResponseParser.parseMonthlyTable(monthTable); parseErrors.addAll(result.errors); allEntries.addAll(result.schedule.entries); diff --git a/lib/schedule/service/rapla/rapla_week_response_parser.dart b/lib/schedule/service/rapla/rapla_week_response_parser.dart index 6d27a811..46fae222 100644 --- a/lib/schedule/service/rapla/rapla_week_response_parser.dart +++ b/lib/schedule/service/rapla/rapla_week_response_parser.dart @@ -63,7 +63,7 @@ class RaplaWeekResponseParser { } allEntries.sort( - (ScheduleEntry e1, ScheduleEntry e2) => e1?.start?.compareTo(e2?.start), + (ScheduleEntry e1, ScheduleEntry e2) => e1.start.compareTo(e2.start), ); return ScheduleQueryResult( @@ -100,7 +100,7 @@ class RaplaWeekResponseParser { // selected year in the date selector var comboBoxes = document.getElementsByTagName("select"); - String year; + String? year; for (var box in comboBoxes) { if (box.attributes.containsKey("name") && box.attributes["name"] == "year") { diff --git a/lib/schedule/service/schedule_prettifier.dart b/lib/schedule/service/schedule_prettifier.dart index a5f9d00e..258bd9cf 100644 --- a/lib/schedule/service/schedule_prettifier.dart +++ b/lib/schedule/service/schedule_prettifier.dart @@ -2,8 +2,10 @@ import 'package:dhbwstudentapp/schedule/model/schedule.dart'; import 'package:dhbwstudentapp/schedule/model/schedule_entry.dart'; class SchedulePrettifier { - final RegExp onlinePrefixRegExp = RegExp(r'\(?online\)?([ -]*)', caseSensitive: false); - final RegExp onlineSuffixRegExp = RegExp(r'([ -]*)\(?online\)?', caseSensitive: false); + final RegExp onlinePrefixRegExp = + RegExp(r'\(?online\)?([ -]*)', caseSensitive: false); + final RegExp onlineSuffixRegExp = + RegExp(r'([ -]*)\(?online\)?', caseSensitive: false); Schedule prettifySchedule(Schedule schedule) { var allEntries = []; @@ -41,20 +43,18 @@ class SchedulePrettifier { } ScheduleEntry _removeCourseFromTitle(ScheduleEntry entry) { - var title = entry.title ?? ""; - var details = entry.details ?? ""; + var title = entry.title; + var details = entry.details; var titleRegex = - RegExp("[A-Z]{3,}-?[A-Z]+[0-9]*[A-Z]*[0-9]*[\/]?[A-Z]*[0-9]*[ ]*-?"); + RegExp("[A-Z]{3,}-?[A-Z]+[0-9]*[A-Z]*[0-9]*[\/]?[A-Z]*[0-9]*[ ]*-?"); var match = titleRegex.firstMatch(entry.title); if (match != null && match.start == 0) { details = title.substring(0, match.end) + " - $details"; title = title.substring(match.end).trim(); } else { - var first = title - .split(" ") - .first; + var first = title.split(" ").first; // Prettify titles: T3MB9025 Fluidmechanik -> Fluidmechanik @@ -62,9 +62,7 @@ class SchedulePrettifier { // or less than 2 charcters long if (!(first == first.toUpperCase() && first.length >= 3)) return entry; - var numberCount = first - .split(new RegExp("[0-9]")) - .length; + var numberCount = first.split(new RegExp("[0-9]")).length; // If there are less thant two numbers in the title, do not prettify it if (numberCount < 2) return entry; diff --git a/lib/schedule/service/schedule_source.dart b/lib/schedule/service/schedule_source.dart index 87f08c7c..6bd044b3 100644 --- a/lib/schedule/service/schedule_source.dart +++ b/lib/schedule/service/schedule_source.dart @@ -9,21 +9,21 @@ abstract class ScheduleSource { /// Returns a future which gives the updated schedule or throws an exception /// if an error happened or the operation was cancelled /// - Future querySchedule(DateTime from, DateTime to, - [CancellationToken cancellationToken]); + Future querySchedule(DateTime? from, DateTime? to, + [CancellationToken? cancellationToken]); bool canQuery(); } class ScheduleQueryFailedException implements Exception { - final dynamic innerException; - final StackTrace trace; + final Object innerException; + final StackTrace? trace; ScheduleQueryFailedException(this.innerException, [this.trace]); @override String toString() { - return (innerException?.toString() ?? "") + "\n" + trace?.toString(); + return innerException.toString() + "\n" + (trace?.toString() ?? ""); } } diff --git a/lib/schedule/ui/dailyschedule/daily_schedule_page.dart b/lib/schedule/ui/dailyschedule/daily_schedule_page.dart index 8f4342e2..b0f19659 100644 --- a/lib/schedule/ui/dailyschedule/daily_schedule_page.dart +++ b/lib/schedule/ui/dailyschedule/daily_schedule_page.dart @@ -1,5 +1,4 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/common/ui/text_styles.dart'; import 'package:dhbwstudentapp/schedule/ui/dailyschedule/widgets/current_time_indicator_widget.dart'; import 'package:dhbwstudentapp/schedule/ui/dailyschedule/widgets/daily_schedule_entry_widget.dart'; @@ -11,16 +10,17 @@ import 'package:property_change_notifier/property_change_notifier.dart'; import 'package:provider/provider.dart'; class DailySchedulePage extends StatefulWidget { + const DailySchedulePage({Key? key}) : super(key: key); @override _DailySchedulePageState createState() => _DailySchedulePageState(); } class _DailySchedulePageState extends State { - DailyScheduleViewModel viewModel; + late DailyScheduleViewModel viewModel; @override Widget build(BuildContext context) { - viewModel = Provider.of(context); + viewModel = Provider.of(context); return PropertyChangeProvider( value: viewModel, @@ -34,18 +34,18 @@ class _DailySchedulePageState extends State { Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 16), child: PropertyChangeConsumer( - builder: (BuildContext context, DailyScheduleViewModel model, - Set properties) { + builder: (BuildContext context, DailyScheduleViewModel? model, + Set? properties) { var dateFormat = DateFormat.yMMMMEEEEd( L.of(context).locale.languageCode); return Text( - dateFormat.format(model.currentDate), + dateFormat.format(model!.currentDate!), style: textStyleDailyScheduleCurrentDate(context), ); }, ), ), - (viewModel.daySchedule?.entries?.length ?? 0) == 0 + viewModel.daySchedule.entries.isEmpty ? Padding( padding: const EdgeInsets.fromLTRB(0, 32, 0, 0), child: Column( @@ -93,7 +93,7 @@ class _DailySchedulePageState extends State { var now = DateTime.now(); var nowIndicatorInserted = false; - for (var entry in viewModel.daySchedule?.entries) { + for (var entry in viewModel.daySchedule.entries) { if (!nowIndicatorInserted && (entry.end.isAfter(now))) { entryWidgets.add(CurrentTimeIndicatorWidget()); nowIndicatorInserted = true; diff --git a/lib/schedule/ui/dailyschedule/widgets/daily_schedule_entry_widget.dart b/lib/schedule/ui/dailyschedule/widgets/daily_schedule_entry_widget.dart index 9c0fe1e1..474c02cd 100644 --- a/lib/schedule/ui/dailyschedule/widgets/daily_schedule_entry_widget.dart +++ b/lib/schedule/ui/dailyschedule/widgets/daily_schedule_entry_widget.dart @@ -9,13 +9,11 @@ import 'package:intl/intl.dart'; class DailyScheduleEntryWidget extends StatelessWidget { final ScheduleEntry scheduleEntry; - const DailyScheduleEntryWidget({Key key, this.scheduleEntry}) + const DailyScheduleEntryWidget({Key? key, required this.scheduleEntry}) : super(key: key); @override Widget build(BuildContext context) { - if (scheduleEntry == null) return Container(); - var timeFormatter = DateFormat.Hm(L.of(context).locale.languageCode); var startTime = timeFormatter.format(scheduleEntry.start); @@ -96,7 +94,7 @@ class DailyScheduleEntryWidget extends StatelessWidget { child: Padding( padding: const EdgeInsets.fromLTRB(32, 0, 0, 0), child: Text( - scheduleEntry.room ?? "", + scheduleEntry.room, softWrap: true, textAlign: TextAlign.end, ), diff --git a/lib/schedule/ui/notification/next_day_information_notification.dart b/lib/schedule/ui/notification/next_day_information_notification.dart index d70ad2ce..d477ae87 100644 --- a/lib/schedule/ui/notification/next_day_information_notification.dart +++ b/lib/schedule/ui/notification/next_day_information_notification.dart @@ -40,8 +40,8 @@ class NextDayInformationNotification extends TaskCallback { if (nextScheduleEntry == null) return; var format = DateFormat.Hm(); - var daysToNextEntry = toStartOfDay(nextScheduleEntry.start) - .difference(toStartOfDay(now)) + var daysToNextEntry = toStartOfDay(nextScheduleEntry.start)! + .difference(toStartOfDay(now)!) .inDays; if (daysToNextEntry > 1) return; @@ -58,27 +58,30 @@ class NextDayInformationNotification extends TaskCallback { ); } - String _getNotificationMessage( + String? _getNotificationMessage( int daysToNextEntry, ScheduleEntry nextScheduleEntry, DateFormat format) { - String message; - if (daysToNextEntry == 0) { - message = interpolate( - _localization.notificationNextClassNextClassAtMessage, - [ - nextScheduleEntry.title, - format.format(nextScheduleEntry.start), - ], - ); - } else if (daysToNextEntry == 1) { - message = interpolate( - _localization.notificationNextClassTomorrow, - [ - nextScheduleEntry.title, - format.format(nextScheduleEntry.start), - ], - ); + switch (daysToNextEntry) { + case 0: + return interpolate( + _localization.notificationNextClassNextClassAtMessage, + [ + nextScheduleEntry.title, + format.format(nextScheduleEntry.start), + ], + ); + + case 1: + return interpolate( + _localization.notificationNextClassTomorrow, + [ + nextScheduleEntry.title, + format.format(nextScheduleEntry.start), + ], + ); + + default: + return null; } - return message; } @override diff --git a/lib/schedule/ui/schedule_navigation_entry.dart b/lib/schedule/ui/schedule_navigation_entry.dart index 096dc47f..bfb542a7 100644 --- a/lib/schedule/ui/schedule_navigation_entry.dart +++ b/lib/schedule/ui/schedule_navigation_entry.dart @@ -1,5 +1,4 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/schedule/ui/schedule_page.dart'; import 'package:dhbwstudentapp/schedule/ui/viewmodels/schedule_view_model.dart'; import 'package:dhbwstudentapp/schedule/ui/weeklyschedule/filter/schedule_filter_page.dart'; @@ -9,23 +8,15 @@ import 'package:flutter/material.dart'; import 'package:kiwi/kiwi.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; -class ScheduleNavigationEntry extends NavigationEntry { - ScheduleViewModel _viewModel; - +class ScheduleNavigationEntry extends NavigationEntry { @override - Widget icon(BuildContext context) { - return Icon(Icons.calendar_today); - } + Icon icon = Icon(Icons.calendar_today); @override - BaseViewModel initViewModel() { - if (_viewModel != null) return _viewModel; - - _viewModel = ScheduleViewModel( + ScheduleViewModel initViewModel() { + return ScheduleViewModel( KiwiContainer().resolve(), ); - - return _viewModel; } @override @@ -40,29 +31,29 @@ class ScheduleNavigationEntry extends NavigationEntry { @override List appBarActions(BuildContext context) { - initViewModel(); return [ PropertyChangeProvider( - value: _viewModel, + value: model, child: PropertyChangeConsumer( properties: const ["didSetupProperly"], - builder: (BuildContext _, ScheduleViewModel __, Set ___) => - _viewModel.didSetupProperly - ? Container() - : IconButton( - icon: Icon(Icons.help_outline), - onPressed: () async { - await ScheduleHelpDialog().show(context); - }, - tooltip: L.of(context).helpButtonTooltip, - )), + builder: + (BuildContext _, ScheduleViewModel? __, Set? ___) => + model.didSetupProperly + ? Container() + : IconButton( + icon: Icon(Icons.help_outline), + onPressed: () async { + await ScheduleHelpDialog().show(context); + }, + tooltip: L.of(context).helpButtonTooltip, + )), ), PropertyChangeProvider( - value: _viewModel, + value: model, child: PropertyChangeConsumer( properties: const ["didSetupProperly"], - builder: (BuildContext _, ScheduleViewModel __, Set ___) => - _viewModel.didSetupProperly + builder: (BuildContext _, ScheduleViewModel? __, Set? ___) => + model.didSetupProperly ? IconButton( icon: Icon(Icons.filter_alt), onPressed: () async { diff --git a/lib/schedule/ui/schedule_page.dart b/lib/schedule/ui/schedule_page.dart index 3da65712..531a0687 100644 --- a/lib/schedule/ui/schedule_page.dart +++ b/lib/schedule/ui/schedule_page.dart @@ -1,5 +1,4 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/schedule/ui/dailyschedule/daily_schedule_page.dart'; import 'package:dhbwstudentapp/schedule/ui/viewmodels/daily_schedule_view_model.dart'; import 'package:dhbwstudentapp/schedule/ui/viewmodels/schedule_view_model.dart'; @@ -12,6 +11,7 @@ import 'package:kiwi/kiwi.dart'; import 'package:provider/provider.dart'; class SchedulePage extends StatefulWidget { + const SchedulePage({Key? key}) : super(key: key); @override _SchedulePageState createState() => _SchedulePageState(); } @@ -29,20 +29,20 @@ class _SchedulePageState extends State { @override Widget build(BuildContext context) { - ScheduleViewModel viewModel = Provider.of(context); + ScheduleViewModel viewModel = Provider.of(context); if (!viewModel.didSetupProperly) { return ScheduleEmptyState(); } else { return PagerWidget( - pages: [ - PageDefinition( + pages: [ + PageDefinition( icon: Icon(Icons.view_week), text: L.of(context).pageWeekOverviewTitle, builder: (_) => WeeklySchedulePage(), viewModel: weeklyScheduleViewModel, ), - PageDefinition( + PageDefinition( icon: Icon(Icons.view_day), text: L.of(context).pageDayOverviewTitle, builder: (_) => DailySchedulePage(), diff --git a/lib/schedule/ui/viewmodels/daily_schedule_view_model.dart b/lib/schedule/ui/viewmodels/daily_schedule_view_model.dart index 793b76ab..0183aa0d 100644 --- a/lib/schedule/ui/viewmodels/daily_schedule_view_model.dart +++ b/lib/schedule/ui/viewmodels/daily_schedule_view_model.dart @@ -8,9 +8,9 @@ class DailyScheduleViewModel extends BaseViewModel { final ScheduleProvider scheduleProvider; - DateTime currentDate; + DateTime? currentDate; - Schedule daySchedule; + Schedule? _daySchedule; DailyScheduleViewModel(this.scheduleProvider) { scheduleProvider.addScheduleUpdatedCallback(_scheduleUpdatedCallback); @@ -18,11 +18,13 @@ class DailyScheduleViewModel extends BaseViewModel { loadScheduleForToday(); } - Future setSchedule(Schedule schedule) async { - daySchedule = schedule; + set schedule(Schedule schedule) { + _daySchedule = schedule; notifyListeners("daySchedule"); } + Schedule get daySchedule => _daySchedule ??= Schedule(); + Future loadScheduleForToday() async { var now = DateTime.now(); currentDate = toStartOfDay(now); @@ -35,30 +37,24 @@ class DailyScheduleViewModel extends BaseViewModel { } Future _updateScheduleFromCache() async { - setSchedule( - await scheduleProvider.getCachedSchedule( - currentDate, - tomorrow(currentDate), - ), + schedule = await scheduleProvider.getCachedSchedule( + currentDate!, + tomorrow(currentDate)!, ); } Future _scheduleUpdatedCallback( Schedule schedule, - DateTime start, - DateTime end, + DateTime? start, + DateTime? end, ) async { - if (schedule == null) return; - start = toStartOfDay(start); end = toStartOfDay(tomorrow(end)); - if (!(start.isAfter(currentDate) || end.isBefore(currentDate))) { - setSchedule( - schedule.trim( - toStartOfDay(currentDate), - toStartOfDay(tomorrow(currentDate)), - ), + if (!(start!.isAfter(currentDate!) || end!.isBefore(currentDate!))) { + schedule = schedule.trim( + toStartOfDay(currentDate), + toStartOfDay(tomorrow(currentDate)), ); } } diff --git a/lib/schedule/ui/viewmodels/weekly_schedule_view_model.dart b/lib/schedule/ui/viewmodels/weekly_schedule_view_model.dart index af98bb16..076eef85 100644 --- a/lib/schedule/ui/viewmodels/weekly_schedule_view_model.dart +++ b/lib/schedule/ui/viewmodels/weekly_schedule_view_model.dart @@ -18,11 +18,11 @@ class WeeklyScheduleViewModel extends BaseViewModel { final ScheduleProvider scheduleProvider; final ScheduleSourceProvider scheduleSourceProvider; - DateTime currentDateStart; - DateTime currentDateEnd; + DateTime? currentDateStart; + DateTime? currentDateEnd; - DateTime clippedDateStart; - DateTime clippedDateEnd; + DateTime? clippedDateStart; + DateTime? clippedDateEnd; bool didUpdateScheduleIntoFuture = true; @@ -33,24 +33,24 @@ class WeeklyScheduleViewModel extends BaseViewModel { bool get hasQueryErrors => _hasQueryErrors; - VoidCallback _queryFailedCallback; + VoidCallback? _queryFailedCallback; bool updateFailed = false; bool isUpdating = false; - Schedule weekSchedule; + Schedule? weekSchedule; - String scheduleUrl; + String? scheduleUrl; DateTime get now => DateTime.now(); - Timer _errorResetTimer; - Timer _updateNowTimer; + Timer? _errorResetTimer; + Timer? _updateNowTimer; final CancelableMutex _updateMutex = CancelableMutex(); - DateTime lastRequestedStart; - DateTime lastRequestedEnd; + DateTime? lastRequestedStart; + DateTime? lastRequestedEnd; WeeklyScheduleViewModel( this.scheduleProvider, @@ -62,7 +62,7 @@ class WeeklyScheduleViewModel extends BaseViewModel { Future _initViewModel() async { _setSchedule( null, - toDayOfWeek(DateTime.now(), DateTime.monday), + toDayOfWeek(DateTime.now(), DateTime.monday)!, toDayOfWeek(DateTime.now(), DateTime.friday), ); @@ -80,15 +80,15 @@ class WeeklyScheduleViewModel extends BaseViewModel { if (setupSuccess) await updateSchedule(currentDateStart, currentDateEnd); } - void _setSchedule(Schedule schedule, DateTime start, DateTime end) { + void _setSchedule(Schedule? schedule, DateTime start, DateTime? end) { weekSchedule = schedule; didUpdateScheduleIntoFuture = currentDateStart?.isBefore(start) ?? true; currentDateStart = start; currentDateEnd = end; if (weekSchedule != null) { - var scheduleStart = weekSchedule.getStartDate(); - var scheduleEnd = weekSchedule.getEndDate(); + var scheduleStart = weekSchedule!.getStartDate(); + var scheduleEnd = weekSchedule!.getEndDate(); if (scheduleStart == null && scheduleEnd == null) { clippedDateStart = toDayOfWeek(start, DateTime.monday); @@ -98,7 +98,7 @@ class WeeklyScheduleViewModel extends BaseViewModel { clippedDateEnd = toDayOfWeek(scheduleEnd, DateTime.friday); } - if (scheduleEnd?.isAfter(clippedDateEnd) ?? false) + if (scheduleEnd?.isAfter(clippedDateEnd!) ?? false) clippedDateEnd = scheduleEnd; displayStartHour = weekSchedule?.getStartTime()?.hour ?? 23; @@ -136,7 +136,7 @@ class WeeklyScheduleViewModel extends BaseViewModel { await updateSchedule(currentDateStart, currentDateEnd); } - Future updateSchedule(DateTime start, DateTime end) async { + Future updateSchedule(DateTime? start, DateTime? end) async { lastRequestedEnd = end; lastRequestedStart = start; @@ -151,8 +151,9 @@ class WeeklyScheduleViewModel extends BaseViewModel { isUpdating = true; notifyListeners("isUpdating"); - await _doUpdateSchedule(start, end); - } catch (_) {} finally { + await _doUpdateSchedule(start!, end!); + } catch (_) { + } finally { isUpdating = false; _updateMutex.release(); notifyListeners("isUpdating"); @@ -162,7 +163,7 @@ class WeeklyScheduleViewModel extends BaseViewModel { Future _doUpdateSchedule(DateTime start, DateTime end) async { print("Refreshing schedule..."); - var cancellationToken = _updateMutex.token; + var cancellationToken = _updateMutex.token!; scheduleUrl = null; @@ -178,7 +179,7 @@ class WeeklyScheduleViewModel extends BaseViewModel { cancellationToken.throwIfCancelled(); if (updatedSchedule?.schedule != null) { - var schedule = updatedSchedule.schedule; + var schedule = updatedSchedule!.schedule; _setSchedule(schedule, start, end); @@ -202,7 +203,7 @@ class WeeklyScheduleViewModel extends BaseViewModel { print("Refreshing done"); } - Future _readScheduleFromService( + Future _readScheduleFromService( DateTime start, DateTime end, CancellationToken token, @@ -213,14 +214,15 @@ class WeeklyScheduleViewModel extends BaseViewModel { end, token, ); - } on OperationCancelledException {} on ScheduleQueryFailedException {} + } on OperationCancelledException { + } on ScheduleQueryFailedException {} return null; } void _cancelErrorInFuture() { if (_errorResetTimer != null) { - _errorResetTimer.cancel(); + _errorResetTimer!.cancel(); } _errorResetTimer = Timer( @@ -233,7 +235,7 @@ class WeeklyScheduleViewModel extends BaseViewModel { } void ensureUpdateNowTimerRunning() { - if (_updateNowTimer == null || !_updateNowTimer.isActive) { + if (_updateNowTimer == null || !_updateNowTimer!.isActive) { _updateNowTimer = Timer.periodic(const Duration(minutes: 1), (_) { notifyListeners("now"); }); diff --git a/lib/schedule/ui/weeklyschedule/filter/filter_view_model.dart b/lib/schedule/ui/weeklyschedule/filter/filter_view_model.dart index d2555c59..29fa0433 100644 --- a/lib/schedule/ui/weeklyschedule/filter/filter_view_model.dart +++ b/lib/schedule/ui/weeklyschedule/filter/filter_view_model.dart @@ -21,7 +21,7 @@ class FilterViewModel extends BaseViewModel { var filteredNames = await _scheduleFilterRepository.queryAllHiddenNames(); - allNames.sort((s1, s2) => s1.compareTo(s2)); + allNames.sort((s1, s2) => s1!.compareTo(s2!)); filterStates = allNames.map((e) { var isFiltered = filteredNames.contains(e); @@ -33,7 +33,7 @@ class FilterViewModel extends BaseViewModel { void applyFilter() async { var allFilteredNames = filterStates - .where((element) => !element.isDisplayed) + .where((element) => !element.isDisplayed!) .map((e) => e.entryName) .toList(); @@ -44,8 +44,8 @@ class FilterViewModel extends BaseViewModel { } class ScheduleEntryFilterState { - bool isDisplayed; - String entryName; + bool? isDisplayed; + String? entryName; ScheduleEntryFilterState(this.isDisplayed, this.entryName); } diff --git a/lib/schedule/ui/weeklyschedule/filter/schedule_filter_page.dart b/lib/schedule/ui/weeklyschedule/filter/schedule_filter_page.dart index 735392eb..3d5229b6 100644 --- a/lib/schedule/ui/weeklyschedule/filter/schedule_filter_page.dart +++ b/lib/schedule/ui/weeklyschedule/filter/schedule_filter_page.dart @@ -46,11 +46,11 @@ class ScheduleFilterPage extends StatelessWidget { child: PropertyChangeProvider( value: _viewModel, child: PropertyChangeConsumer( - builder: (BuildContext _, FilterViewModel viewModel, - Set ___) => + builder: (BuildContext _, FilterViewModel? viewModel, + Set? ___) => ListView.builder( shrinkWrap: true, - itemCount: viewModel.filterStates.length, + itemCount: viewModel!.filterStates.length, itemBuilder: (context, index) => FilterStateRow(viewModel.filterStates[index]), )), @@ -74,7 +74,7 @@ class FilterStateRow extends StatefulWidget { } class _FilterStateRowState extends State { - bool isChecked = false; + bool? isChecked = false; @override void initState() { @@ -94,7 +94,7 @@ class _FilterStateRowState extends State { }); }, controlAffinity: ListTileControlAffinity.leading, - title: Text(widget.filterState.entryName), + title: Text(widget.filterState.entryName!), ); } } diff --git a/lib/schedule/ui/weeklyschedule/schedule_entry_detail_bottom_sheet.dart b/lib/schedule/ui/weeklyschedule/schedule_entry_detail_bottom_sheet.dart index a5ad1ea1..377906fd 100644 --- a/lib/schedule/ui/weeklyschedule/schedule_entry_detail_bottom_sheet.dart +++ b/lib/schedule/ui/weeklyschedule/schedule_entry_detail_bottom_sheet.dart @@ -8,19 +8,19 @@ import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; class ScheduleEntryDetailBottomSheet extends StatelessWidget { - final ScheduleEntry scheduleEntry; + final ScheduleEntry? scheduleEntry; - const ScheduleEntryDetailBottomSheet({Key key, this.scheduleEntry}) + const ScheduleEntryDetailBottomSheet({Key? key, this.scheduleEntry}) : super(key: key); @override Widget build(BuildContext context) { var formatter = DateFormat.Hm(L.of(context).locale.languageCode); - var timeStart = formatter.format(scheduleEntry.start); - var timeEnd = formatter.format(scheduleEntry.end); + var timeStart = formatter.format(scheduleEntry!.start); + var timeEnd = formatter.format(scheduleEntry!.end); var typeString = - scheduleEntryTypeToReadableString(context, scheduleEntry.type); + scheduleEntryTypeToReadableString(context, scheduleEntry!.type); return Container( height: 400, @@ -90,7 +90,7 @@ class ScheduleEntryDetailBottomSheet extends StatelessWidget { child: Padding( padding: const EdgeInsets.fromLTRB(16, 0, 0, 0), child: Text( - scheduleEntry.title ?? "", + scheduleEntry!.title, softWrap: true, style: textStyleScheduleEntryBottomPageTitle(context), ), @@ -106,7 +106,7 @@ class ScheduleEntryDetailBottomSheet extends StatelessWidget { children: [ Expanded( child: Text( - scheduleEntry.professor ?? "", + scheduleEntry!.professor, ), ), Text( @@ -116,13 +116,13 @@ class ScheduleEntryDetailBottomSheet extends StatelessWidget { ], ), ), - scheduleEntry.room?.isEmpty ?? true + scheduleEntry!.room.isEmpty ? Container() : Padding( padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), - child: Text(scheduleEntry.room.replaceAll(",", "\n")), + child: Text(scheduleEntry!.room.replaceAll(",", "\n")), ), - scheduleEntry.details?.isEmpty ?? true + scheduleEntry!.details.isEmpty ? Container() : Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 16), @@ -131,9 +131,9 @@ class ScheduleEntryDetailBottomSheet extends StatelessWidget { height: 1, ), ), - scheduleEntry.details?.isEmpty ?? true + scheduleEntry!.details.isEmpty ? Container() - : Text(scheduleEntry.details), + : Text(scheduleEntry!.details), ], ), ), diff --git a/lib/schedule/ui/weeklyschedule/weekly_schedule_page.dart b/lib/schedule/ui/weeklyschedule/weekly_schedule_page.dart index baa28690..4e7f77e5 100644 --- a/lib/schedule/ui/weeklyschedule/weekly_schedule_page.dart +++ b/lib/schedule/ui/weeklyschedule/weekly_schedule_page.dart @@ -13,13 +13,15 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; class WeeklySchedulePage extends StatefulWidget { + const WeeklySchedulePage({Key? key}) : super(key: key); + @override _WeeklySchedulePageState createState() => _WeeklySchedulePageState(); } class _WeeklySchedulePageState extends State { - WeeklyScheduleViewModel viewModel; - Schedule schedule; + late WeeklyScheduleViewModel viewModel; + Schedule? schedule; _WeeklySchedulePageState(); @@ -29,7 +31,7 @@ class _WeeklySchedulePageState extends State { } void _showQueryFailedSnackBar() { - Scaffold.of(context).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Column( crossAxisAlignment: CrossAxisAlignment.end, @@ -40,7 +42,7 @@ class _WeeklySchedulePageState extends State { child: Text( L.of(context).scheduleQueryFailedOpenInBrowser.toUpperCase()), onPressed: () { - launchUrl(Uri.parse(viewModel.scheduleUrl)); + launchUrl(Uri.parse(viewModel.scheduleUrl!)); }, ) ], @@ -51,15 +53,15 @@ class _WeeklySchedulePageState extends State { } void _previousWeek() async { - await viewModel?.previousWeek(); + await viewModel.previousWeek(); } void _nextWeek() async { - await viewModel?.nextWeek(); + await viewModel.nextWeek(); } void _goToToday() async { - await viewModel?.goToToday(); + await viewModel.goToToday(); } void _onScheduleEntryTap(BuildContext context, ScheduleEntry entry) { @@ -77,7 +79,7 @@ class _WeeklySchedulePageState extends State { @override Widget build(BuildContext context) { - viewModel = Provider.of(context); + viewModel = Provider.of(context); viewModel.ensureUpdateNowTimerRunning(); viewModel.setQueryFailedCallback(_showQueryFailedSnackBar); @@ -109,9 +111,9 @@ class _WeeklySchedulePageState extends State { child: PropertyChangeConsumer( properties: const ["weekSchedule", "now"], builder: (BuildContext context, - WeeklyScheduleViewModel model, Set properties) { + WeeklyScheduleViewModel? model, Set? _) { return PageTransitionSwitcher( - reverse: !model.didUpdateScheduleIntoFuture, + reverse: !model!.didUpdateScheduleIntoFuture, duration: const Duration(milliseconds: 300), transitionBuilder: (Widget child, Animation animation, @@ -125,7 +127,7 @@ class _WeeklySchedulePageState extends State { ), child: ScheduleWidget( key: ValueKey( - model.currentDateStart.toIso8601String(), + model.currentDateStart!.toIso8601String(), ), schedule: model.weekSchedule, displayStart: model.clippedDateStart ?? @@ -146,8 +148,8 @@ class _WeeklySchedulePageState extends State { PropertyChangeConsumer( properties: const ["isUpdating"], builder: (BuildContext context, - WeeklyScheduleViewModel model, Set properties) { - return model.isUpdating + WeeklyScheduleViewModel? model, Set? _) { + return model!.isUpdating ? const LinearProgressIndicator() : Container(); }, @@ -189,10 +191,9 @@ class _WeeklySchedulePageState extends State { properties: const [ "updateFailed", ], - builder: (BuildContext context, WeeklyScheduleViewModel model, - Set properties) => + builder: (BuildContext context, WeeklyScheduleViewModel? model, Set? _) => ErrorDisplay( - show: model.updateFailed, + show: model!.updateFailed, ), ); } diff --git a/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_alignment.dart b/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_alignment.dart index f1748038..4a753c55 100644 --- a/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_alignment.dart +++ b/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_alignment.dart @@ -3,8 +3,8 @@ import 'package:dhbwstudentapp/schedule/model/schedule_entry.dart'; class ScheduleEntryAlignmentInformation { final ScheduleEntry entry; - double leftColumn; - double rightColumn; + late double leftColumn; + late double rightColumn; ScheduleEntryAlignmentInformation(this.entry); } @@ -34,7 +34,7 @@ class ScheduleEntryAlignmentAlgorithm { List> columns = [[]]; - DateTime lastEventEnd; + DateTime? lastEventEnd; for (var event in events) { if (!event.entry.start diff --git a/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_widget.dart b/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_widget.dart index 8511ad5c..db798f14 100644 --- a/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_widget.dart +++ b/lib/schedule/ui/weeklyschedule/widgets/schedule_entry_widget.dart @@ -7,11 +7,11 @@ typedef ScheduleEntryTapCallback = Function(ScheduleEntry entry); class ScheduleEntryWidget extends StatelessWidget { final ScheduleEntry scheduleEntry; - final ScheduleEntryTapCallback onScheduleEntryTap; + final ScheduleEntryTapCallback? onScheduleEntryTap; const ScheduleEntryWidget({ - Key key, - this.scheduleEntry, + Key? key, + required this.scheduleEntry, this.onScheduleEntryTap, }) : super(key: key); @@ -25,7 +25,7 @@ class ScheduleEntryWidget extends StatelessWidget { margin: const EdgeInsets.fromLTRB(1, 0, 1, 0), child: InkWell( onTap: () { - if (onScheduleEntryTap != null) onScheduleEntryTap(scheduleEntry); + if (onScheduleEntryTap != null) onScheduleEntryTap!(scheduleEntry); }, child: Padding( padding: const EdgeInsets.fromLTRB(4, 4, 4, 4), diff --git a/lib/schedule/ui/weeklyschedule/widgets/schedule_grid.dart b/lib/schedule/ui/weeklyschedule/widgets/schedule_grid.dart index c306466c..361e9249 100644 --- a/lib/schedule/ui/weeklyschedule/widgets/schedule_grid.dart +++ b/lib/schedule/ui/weeklyschedule/widgets/schedule_grid.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; class ScheduleGrid extends CustomPaint { - final int fromHour; - final int toHour; + final int? fromHour; + final int? toHour; final double timeLabelsWidth; final double dateLabelsHeight; final int columns; @@ -23,8 +23,8 @@ class ScheduleGrid extends CustomPaint { } class ScheduleGridCustomPaint extends CustomPainter { - final int fromHour; - final int toHour; + final int? fromHour; + final int? toHour; final double timeLabelsWidth; final double dateLabelsHeight; final int columns; @@ -45,7 +45,7 @@ class ScheduleGridCustomPaint extends CustomPainter { ..color = gridLineColor ..strokeWidth = 1; - var lines = toHour - fromHour; + var lines = toHour! - fromHour!; drawHorizontalLines(lines, size, canvas, secondaryPaint); drawVerticalLines(size, canvas, secondaryPaint); diff --git a/lib/schedule/ui/weeklyschedule/widgets/schedule_past_overlay.dart b/lib/schedule/ui/weeklyschedule/widgets/schedule_past_overlay.dart index 94234607..ae98def9 100644 --- a/lib/schedule/ui/weeklyschedule/widgets/schedule_past_overlay.dart +++ b/lib/schedule/ui/weeklyschedule/widgets/schedule_past_overlay.dart @@ -5,11 +5,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; class SchedulePastOverlay extends CustomPaint { - final DateTime fromDate; - final DateTime toDate; - final DateTime now; - final int fromHour; - final int toHour; + final DateTime? fromDate; + final DateTime? toDate; + final DateTime? now; + final int? fromHour; + final int? toHour; final int columns; final Color overlayColor; @@ -35,11 +35,11 @@ class SchedulePastOverlay extends CustomPaint { } class SchedulePastOverlayCustomPaint extends CustomPainter { - final DateTime fromDate; - final DateTime toDate; - final DateTime now; - final int fromHour; - final int toHour; + final DateTime? fromDate; + final DateTime? toDate; + final DateTime? now; + final int? fromHour; + final int? toHour; final int columns; final Color overlayColor; @@ -59,9 +59,9 @@ class SchedulePastOverlayCustomPaint extends CustomPainter { ..color = overlayColor ..strokeWidth = 1; - if (now.isBefore(toDate) && now.isAfter(fromDate)) { + if (now!.isBefore(toDate!) && now!.isAfter(fromDate!)) { drawPartialPastOverlay(canvas, size, overlayPaint); - } else if (now.isAfter(toDate)) { + } else if (now!.isAfter(toDate!)) { drawCompletePastOverlay(canvas, size, overlayPaint); } } @@ -78,7 +78,7 @@ class SchedulePastOverlayCustomPaint extends CustomPainter { } void drawPartialPastOverlay(Canvas canvas, Size size, Paint overlayPaint) { - var difference = now.difference(fromDate); + var difference = now!.difference(fromDate!); var differenceInDays = (difference.inHours / 24).floor(); var dayWidth = size.width / columns; @@ -93,9 +93,9 @@ class SchedulePastOverlayCustomPaint extends CustomPainter { ); var leftoverMinutes = - difference.inMinutes - (differenceInDays * 24 * 60) - (60 * fromHour); + difference.inMinutes - (differenceInDays * 24 * 60) - (60 * fromHour!); - var displayedMinutes = (toHour - fromHour) * 60; + var displayedMinutes = (toHour! - fromHour!) * 60; var leftoverHeight = leftoverMinutes * (size.height / displayedMinutes); if (leftoverHeight > 0) { diff --git a/lib/schedule/ui/weeklyschedule/widgets/schedule_widget.dart b/lib/schedule/ui/weeklyschedule/widgets/schedule_widget.dart index 59fcaeef..748b3290 100644 --- a/lib/schedule/ui/weeklyschedule/widgets/schedule_widget.dart +++ b/lib/schedule/ui/weeklyschedule/widgets/schedule_widget.dart @@ -13,16 +13,16 @@ import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; class ScheduleWidget extends StatelessWidget { - final Schedule schedule; - final DateTime displayStart; - final DateTime displayEnd; - final DateTime now; - final int displayStartHour; - final int displayEndHour; - final ScheduleEntryTapCallback onScheduleEntryTap; + final Schedule? schedule; + final DateTime? displayStart; + final DateTime? displayEnd; + final DateTime? now; + final int? displayStartHour; + final int? displayEndHour; + final ScheduleEntryTapCallback? onScheduleEntryTap; const ScheduleWidget({ - Key key, + Key? key, this.schedule, this.displayStart, this.displayEnd, @@ -48,7 +48,7 @@ class ScheduleWidget extends StatelessWidget { var timeLabelsWidth = 50.0; var hourHeight = - (height - dayLabelsHeight) / (displayEndHour - displayStartHour); + (height - dayLabelsHeight) / (displayEndHour! - displayStartHour!); var minuteHeight = hourHeight / 60; var days = calculateDisplayedDays(); @@ -112,7 +112,7 @@ class ScheduleWidget extends StatelessWidget { int calculateDisplayedDays() { var startEndDifference = - toStartOfDay(displayEnd)?.difference(toStartOfDay(displayStart)); + toStartOfDay(displayEnd)?.difference(toStartOfDay(displayStart)!); var days = (startEndDifference?.inDays ?? 0) + 1; @@ -135,12 +135,12 @@ class ScheduleWidget extends StatelessWidget { ) { var labelWidgets = []; - for (var i = displayStartHour; i < displayEndHour; i++) { + for (var i = displayStartHour!; i < displayEndHour!; i++) { var hourLabelText = i.toString() + ":00"; labelWidgets.add( Positioned( - top: rowHeight * (i - displayStartHour) + dayLabelHeight, + top: rowHeight * (i - displayStartHour!) + dayLabelHeight, left: 0, child: Padding( padding: const EdgeInsets.fromLTRB(4, 4, 4, 8), @@ -155,11 +155,11 @@ class ScheduleWidget extends StatelessWidget { var dayFormatter = DateFormat("E", L.of(context).locale.languageCode); var dateFormatter = DateFormat("d. MMM", L.of(context).locale.languageCode); - var loopEnd = toStartOfDay(tomorrow(displayEnd)); + var loopEnd = toStartOfDay(tomorrow(displayEnd))!; - for (var columnDate = toStartOfDay(displayStart); + for (var columnDate = toStartOfDay(displayStart)!; columnDate.isBefore(loopEnd); - columnDate = tomorrow(columnDate)) { + columnDate = tomorrow(columnDate)!) { labelWidgets.add( Positioned( top: 0, @@ -192,20 +192,20 @@ class ScheduleWidget extends StatelessWidget { List buildEntryWidgets( double hourHeight, double minuteHeight, double width, int columns) { if (schedule == null) return []; - if (schedule.entries.isEmpty) return []; + if (schedule!.entries.isEmpty) return []; var entryWidgets = []; var columnWidth = width / columns; - DateTime columnStartDate = toStartOfDay(displayStart); - DateTime columnEndDate = tomorrow(columnStartDate); + DateTime? columnStartDate = toStartOfDay(displayStart); + DateTime? columnEndDate = tomorrow(columnStartDate); for (int i = 0; i < columns; i++) { var xPosition = columnWidth * i; var maxWidth = columnWidth; - var columnSchedule = schedule.trim(columnStartDate, columnEndDate); + var columnSchedule = schedule!.trim(columnStartDate, columnEndDate); entryWidgets.addAll(buildEntryWidgetsForColumn( maxWidth, @@ -237,10 +237,10 @@ class ScheduleWidget extends StatelessWidget { for (var value in laidOutEntries) { var entry = value.entry; - var yStart = hourHeight * (entry.start.hour - displayStartHour) + + var yStart = hourHeight * (entry.start.hour - displayStartHour!) + minuteHeight * entry.start.minute; - var yEnd = hourHeight * (entry.end.hour - displayStartHour) + + var yEnd = hourHeight * (entry.end.hour - displayStartHour!) + minuteHeight * entry.end.minute; var entryLeft = maxWidth * value.leftColumn; diff --git a/lib/schedule/ui/widgets/enter_dualis_credentials_dialog.dart b/lib/schedule/ui/widgets/enter_dualis_credentials_dialog.dart index cca38e58..eec5214e 100644 --- a/lib/schedule/ui/widgets/enter_dualis_credentials_dialog.dart +++ b/lib/schedule/ui/widgets/enter_dualis_credentials_dialog.dart @@ -9,13 +9,9 @@ class EnterDualisCredentialsDialog { final PreferencesProvider _preferencesProvider; final ScheduleSourceProvider _scheduleSourceProvider; - final TextEditingController _usernameEditingController = - TextEditingController(); - final TextEditingController _passwordEditingController = - TextEditingController(); + final _controller = CredentialsEditingController(); - String username; - String password; + Credentials? _credentials; EnterDualisCredentialsDialog( this._preferencesProvider, @@ -23,9 +19,7 @@ class EnterDualisCredentialsDialog { ); Future show(BuildContext context) async { - var credentials = await _preferencesProvider.loadDualisCredentials(); - username = credentials.username; - password = credentials.password; + _credentials = await _preferencesProvider.loadDualisCredentials(); await showDialog( context: context, @@ -34,11 +28,9 @@ class EnterDualisCredentialsDialog { } AlertDialog _buildDialog(BuildContext context) { - if (_usernameEditingController.text != username) { - _usernameEditingController.text = username; - } - if (_passwordEditingController.text != password) { - _passwordEditingController.text = password; + final credentials = _credentials; + if (credentials != null) { + _controller.credentials = credentials; } return AlertDialog( @@ -55,8 +47,7 @@ class EnterDualisCredentialsDialog { ), ), LoginCredentialsWidget( - usernameEditingController: _usernameEditingController, - passwordEditingController: _passwordEditingController, + controller: _controller, onSubmitted: () async {}, ), ], @@ -70,11 +61,9 @@ class EnterDualisCredentialsDialog { TextButton( child: Text(L.of(context).dialogOk.toUpperCase()), onPressed: () async { + // TODO: [Leptopoda] validate credentials like [DualisLoginViewModel] await _preferencesProvider.storeDualisCredentials( - Credentials( - _usernameEditingController.text, - _passwordEditingController.text, - ), + _controller.credentials, ); await _scheduleSourceProvider.setupForDualis(); diff --git a/lib/schedule/ui/widgets/enter_ical_url.dart b/lib/schedule/ui/widgets/enter_ical_url.dart index 25cc1d17..b563d723 100644 --- a/lib/schedule/ui/widgets/enter_ical_url.dart +++ b/lib/schedule/ui/widgets/enter_ical_url.dart @@ -12,18 +12,18 @@ class EnterIcalDialog extends EnterUrlDialog { EnterIcalDialog(this._preferencesProvider, this._scheduleSource); @override - Future loadUrl() { + Future loadUrl() { return _preferencesProvider.getIcalUrl(); } @override - Future saveUrl(String url) async { + Future saveUrl(String? url) async { await _scheduleSource.setupForIcal(url); } @override - bool isValidUrl(String url) { - return IcalScheduleSource.isValidUrl(url); + bool isValidUrl(String? url) { + return IcalScheduleSource.isValidUrl(url!); } @override diff --git a/lib/schedule/ui/widgets/enter_rapla_url_dialog.dart b/lib/schedule/ui/widgets/enter_rapla_url_dialog.dart index 0bc5167b..c2ed49b2 100644 --- a/lib/schedule/ui/widgets/enter_rapla_url_dialog.dart +++ b/lib/schedule/ui/widgets/enter_rapla_url_dialog.dart @@ -15,7 +15,7 @@ class EnterRaplaUrlDialog extends EnterUrlDialog { EnterRaplaUrlDialog(this._preferencesProvider, this._scheduleSource); @override - Future saveUrl(String url) async { + Future saveUrl(String? url) async { await _scheduleSource.setupForRapla(url); } @@ -25,8 +25,8 @@ class EnterRaplaUrlDialog extends EnterUrlDialog { } @override - bool isValidUrl(String url) { - return RaplaScheduleSource.isValidUrl(url); + bool isValidUrl(String? url) { + return RaplaScheduleSource.isValidUrl(url!); } @override diff --git a/lib/schedule/ui/widgets/enter_url_dialog.dart b/lib/schedule/ui/widgets/enter_url_dialog.dart index 58a65dc4..fc09625a 100644 --- a/lib/schedule/ui/widgets/enter_url_dialog.dart +++ b/lib/schedule/ui/widgets/enter_url_dialog.dart @@ -49,7 +49,7 @@ abstract class EnterUrlDialog { builder: ( BuildContext context, ValueNotifier url, - Widget child, + Widget? child, ) { if (_urlTextController.text != url.value) _urlTextController.text = url.value; @@ -58,7 +58,7 @@ abstract class EnterUrlDialog { builder: ( BuildContext context, ValueNotifier hasUrlError, - Widget child, + Widget? child, ) => TextField( controller: _urlTextController, @@ -103,7 +103,7 @@ abstract class EnterUrlDialog { value: _hasUrlError, child: Consumer( builder: (BuildContext context, ValueNotifier hasUrlError, - Widget child) => + Widget? child) => TextButton( child: Text(L.of(context).dialogOk.toUpperCase()), onPressed: hasUrlError.value @@ -120,15 +120,15 @@ abstract class EnterUrlDialog { } Future _pasteUrl() async { - ClipboardData data = await Clipboard.getData('text/plain'); + ClipboardData? data = await Clipboard.getData('text/plain'); if (data?.text != null) { - _setUrl(data.text); + _setUrl(data!.text!); } } Future _loadUrl() async { - _url.value = await loadUrl(); + _url.value = await loadUrl() ?? ""; } void _setUrl(String url) { @@ -140,9 +140,9 @@ abstract class EnterUrlDialog { _hasUrlError.value = !isValidUrl(_url.value); } - bool isValidUrl(String url); - Future saveUrl(String url); - Future loadUrl(); + bool isValidUrl(String? url); + Future saveUrl(String? url); + Future loadUrl(); String title(BuildContext context); String message(BuildContext context); diff --git a/lib/schedule/ui/widgets/schedule_empty_state.dart b/lib/schedule/ui/widgets/schedule_empty_state.dart index c52a1e59..37c846cc 100644 --- a/lib/schedule/ui/widgets/schedule_empty_state.dart +++ b/lib/schedule/ui/widgets/schedule_empty_state.dart @@ -32,7 +32,7 @@ class ScheduleEmptyState extends StatelessWidget { child: ClipRRect( child: Padding( padding: const EdgeInsets.all(32), - child: Image.asset(image[Theme.of(context).brightness]), + child: Image.asset(image[Theme.of(context).brightness]!), ), ), ) diff --git a/lib/schedule/ui/widgets/select_mannheim_course_dialog.dart b/lib/schedule/ui/widgets/select_mannheim_course_dialog.dart index 70943bb4..e828b847 100644 --- a/lib/schedule/ui/widgets/select_mannheim_course_dialog.dart +++ b/lib/schedule/ui/widgets/select_mannheim_course_dialog.dart @@ -9,7 +9,7 @@ import 'package:property_change_notifier/property_change_notifier.dart'; class SelectMannheimCourseDialog { final ScheduleSourceProvider _scheduleSourceProvider; - MannheimViewModel _mannheimViewModel; + late MannheimViewModel _mannheimViewModel; SelectMannheimCourseDialog( this._scheduleSourceProvider, diff --git a/lib/schedule/ui/widgets/select_source_dialog.dart b/lib/schedule/ui/widgets/select_source_dialog.dart index 83d03fd7..5ec9f9cd 100644 --- a/lib/schedule/ui/widgets/select_source_dialog.dart +++ b/lib/schedule/ui/widgets/select_source_dialog.dart @@ -13,7 +13,7 @@ class SelectSourceDialog { final PreferencesProvider _preferencesProvider; final ScheduleSourceProvider _scheduleSourceProvider; - ScheduleSourceType _currentScheduleSource; + ScheduleSourceType? _currentScheduleSource; SelectSourceDialog(this._preferencesProvider, this._scheduleSourceProvider); @@ -41,31 +41,31 @@ class SelectSourceDialog { RadioListTile( groupValue: _currentScheduleSource, value: ScheduleSourceType.Rapla, - onChanged: (v) => sourceSelected(v, context), + onChanged: (dynamic v) => sourceSelected(v, context), title: Text(L.of(context).scheduleSourceTypeRapla), ), RadioListTile( groupValue: _currentScheduleSource, value: ScheduleSourceType.Dualis, - onChanged: (v) => sourceSelected(v, context), + onChanged: (dynamic v) => sourceSelected(v, context), title: Text(L.of(context).scheduleSourceTypeDualis), ), RadioListTile( groupValue: _currentScheduleSource, value: ScheduleSourceType.Mannheim, - onChanged: (v) => sourceSelected(v, context), + onChanged: (dynamic v) => sourceSelected(v, context), title: Text(L.of(context).scheduleSourceTypeMannheim), ), RadioListTile( groupValue: _currentScheduleSource, value: ScheduleSourceType.Ical, - onChanged: (v) => sourceSelected(v, context), + onChanged: (dynamic v) => sourceSelected(v, context), title: Text(L.of(context).scheduleSourceTypeIcal), ), RadioListTile( groupValue: _currentScheduleSource, value: ScheduleSourceType.None, - onChanged: (v) => sourceSelected(v, context), + onChanged: (dynamic v) => sourceSelected(v, context), title: Text(L.of(context).scheduleSourceTypeNone), ) ], @@ -76,6 +76,7 @@ class SelectSourceDialog { ScheduleSourceType type, BuildContext context, ) async { + // TODO: [Leptopoda] only switch the type when the setup is completed. _preferencesProvider.setScheduleSourceType(type.index); Navigator.of(context).pop(); diff --git a/lib/ui/banner_widget.dart b/lib/ui/banner_widget.dart index 7700d6e2..f993f420 100644 --- a/lib/ui/banner_widget.dart +++ b/lib/ui/banner_widget.dart @@ -5,7 +5,11 @@ class BannerWidget extends StatelessWidget { final String buttonText; final VoidCallback onButtonTap; - const BannerWidget({Key key, this.message, this.buttonText, this.onButtonTap}) + const BannerWidget( + {Key? key, + required this.message, + required this.buttonText, + required this.onButtonTap}) : super(key: key); @override @@ -27,23 +31,19 @@ class BannerWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - message, + Text(message), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 12, 0, 0), + child: TextButton( + child: Text(buttonText), + onPressed: onButtonTap, + ), + ), + ], ), - buttonText != null - ? Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 12, 0, 0), - child: TextButton( - child: Text(buttonText), - onPressed: onButtonTap, - ), - ), - ], - ) - : Container(), ], ), ), diff --git a/lib/ui/login_credentials_widget.dart b/lib/ui/login_credentials_widget.dart index b8ee1134..c3e70936 100644 --- a/lib/ui/login_credentials_widget.dart +++ b/lib/ui/login_credentials_widget.dart @@ -1,45 +1,30 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:flutter/material.dart'; class LoginCredentialsWidget extends StatefulWidget { - final TextEditingController usernameEditingController; - final TextEditingController passwordEditingController; + final CredentialsEditingController controller; final Function onSubmitted; const LoginCredentialsWidget({ - Key key, - this.usernameEditingController, - this.passwordEditingController, - this.onSubmitted, + Key? key, + required this.controller, + required this.onSubmitted, }) : super(key: key); @override - _LoginCredentialsWidgetState createState() => _LoginCredentialsWidgetState( - usernameEditingController, - passwordEditingController, - onSubmitted, - ); + _LoginCredentialsWidgetState createState() => _LoginCredentialsWidgetState(); } class _LoginCredentialsWidgetState extends State { - final TextEditingController _usernameEditingController; - final TextEditingController _passwordEditingController; - final Function _onSubmitted; - final _focus = FocusNode(); - _LoginCredentialsWidgetState( - this._usernameEditingController, - this._passwordEditingController, - this._onSubmitted, - ); - @override Widget build(BuildContext context) { return Column( children: [ TextField( - controller: _usernameEditingController, + controller: widget.controller.username, decoration: InputDecoration( hintText: L.of(context).loginUsername, icon: Icon(Icons.alternate_email), @@ -50,7 +35,7 @@ class _LoginCredentialsWidgetState extends State { textInputAction: TextInputAction.next, ), TextField( - controller: _passwordEditingController, + controller: widget.controller.password, obscureText: true, decoration: InputDecoration( hintText: L.of(context).loginPassword, @@ -58,9 +43,7 @@ class _LoginCredentialsWidgetState extends State { ), focusNode: _focus, onSubmitted: (v) { - if (_onSubmitted != null) { - _onSubmitted(); - } + widget.onSubmitted(); }, ), ], diff --git a/lib/ui/main_page.dart b/lib/ui/main_page.dart index 9584c87c..47cfa706 100644 --- a/lib/ui/main_page.dart +++ b/lib/ui/main_page.dart @@ -15,6 +15,8 @@ import 'package:provider/provider.dart'; /// To navigate to a new route inside this widget use the [NavigatorKey.mainKey] /// class MainPage extends StatefulWidget { + const MainPage({Key? key}) : super(key: key); + @override _MainPageState createState() => _MainPageState(); } @@ -46,7 +48,7 @@ class _MainPageState extends State with NavigatorObserver { return ChangeNotifierProvider.value( value: _currentEntryIndex, child: Consumer>( - builder: (BuildContext context, value, Widget child) { + builder: (BuildContext context, value, Widget? child) { Widget content; if (PlatformUtil.isPhone() || PlatformUtil.isPortrait(context)) { @@ -64,11 +66,11 @@ class _MainPageState extends State with NavigatorObserver { Widget buildPhoneLayout(BuildContext context, Navigator navigator) { return WillPopScope( onWillPop: () async { - var canPop = NavigatorKey.mainKey.currentState.canPop(); + var canPop = NavigatorKey.mainKey.currentState!.canPop(); if (!canPop) return true; - NavigatorKey.mainKey.currentState.pop(); + NavigatorKey.mainKey.currentState!.pop(); return false; }, @@ -135,7 +137,7 @@ class _MainPageState extends State with NavigatorObserver { for (var entry in navigationEntries) { drawerEntries.add(DrawerNavigationEntry( - entry.icon(context), + entry.icon, entry.title(context), )); } @@ -146,7 +148,7 @@ class _MainPageState extends State with NavigatorObserver { void _onNavigationTapped(int index) { _currentEntryIndex.value = index; - NavigatorKey.mainKey.currentState + NavigatorKey.mainKey.currentState! .pushNamedAndRemoveUntil(currentEntry.route, (route) { return route.settings.name == navigationEntries[0].route; }); @@ -160,7 +162,7 @@ class _MainPageState extends State with NavigatorObserver { } } - void updateNavigationDrawer(String routeName) { + void updateNavigationDrawer(String? routeName) { for (int i = 0; i < navigationEntries.length; i++) { if (navigationEntries[i].route == routeName) { _currentEntryIndex.value = i; @@ -170,17 +172,17 @@ class _MainPageState extends State with NavigatorObserver { } @override - void didPop(Route route, Route previousRoute) { - updateNavigationDrawer(previousRoute.settings.name); + void didPop(Route route, Route? previousRoute) { + updateNavigationDrawer(previousRoute!.settings.name); } @override - void didPush(Route route, Route previousRoute) { + void didPush(Route route, Route? previousRoute) { updateNavigationDrawer(route.settings.name); } @override - void didReplace({Route newRoute, Route oldRoute}) { - updateNavigationDrawer(newRoute.settings.name); + void didReplace({Route? newRoute, Route? oldRoute}) { + updateNavigationDrawer(newRoute!.settings.name); } } diff --git a/lib/ui/navigation/navigation_entry.dart b/lib/ui/navigation/navigation_entry.dart index dedbf575..4c123c22 100644 --- a/lib/ui/navigation/navigation_entry.dart +++ b/lib/ui/navigation/navigation_entry.dart @@ -2,38 +2,30 @@ import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -abstract class NavigationEntry { - BaseViewModel _viewModel; +abstract class NavigationEntry { + T? _viewModel; String get route; String title(BuildContext context); - Widget icon(BuildContext context); + Icon get icon; Widget buildRoute(BuildContext context) { - var model = viewModel(); - if (model != null) { - return ChangeNotifierProvider.value( - value: model, - child: build(context), - ); - } else { - return build(context); - } + return ChangeNotifierProvider.value( + value: model, + child: build(context), + ); } Widget build(BuildContext context); - BaseViewModel viewModel() { - if (_viewModel == null) { - _viewModel = initViewModel(); - } - - return _viewModel; + T get model { + return _viewModel ??= initViewModel(); } - BaseViewModel initViewModel() => null; + T initViewModel(); - List appBarActions(BuildContext context) => []; + // TODO: [Leptopoda] clean up but not null related so something for later + List appBarActions(BuildContext context); } diff --git a/lib/ui/navigation/router.dart b/lib/ui/navigation/router.dart index 72c5fe58..f17e4cd7 100644 --- a/lib/ui/navigation/router.dart +++ b/lib/ui/navigation/router.dart @@ -18,7 +18,7 @@ final List navigationEntries = [ Route generateDrawerRoute(RouteSettings settings) { print("=== === === === === === Navigating to: ${settings.name}"); - WidgetBuilder widget; + WidgetBuilder? widget; for (var route in navigationEntries) { if (route.route == settings.name) { @@ -28,14 +28,14 @@ Route generateDrawerRoute(RouteSettings settings) { } if (widget == null) { - print("Failed to navigate to: " + settings.name); + print("Failed to navigate to: " + settings.name!); widget = (BuildContext context) => Container(); } return PageRouteBuilder( settings: settings, transitionDuration: const Duration(milliseconds: 200), - pageBuilder: (context, animation, secondaryAnimation) => widget(context), + pageBuilder: (context, animation, secondaryAnimation) => widget!(context), transitionsBuilder: (context, animation, secondaryAnimation, child) { const offsetBegin = Offset(0.0, 0.005); final offsetEnd = Offset.zero; @@ -76,7 +76,7 @@ Route generateRoute(RouteSettings settings) { target = SettingsPage(); break; default: - print("Failed to navigate to: " + settings.name); + print("Failed to navigate to: " + settings.name!); target = Container(); } diff --git a/lib/ui/navigation_drawer.dart b/lib/ui/navigation_drawer.dart index 6266b583..abc21d8a 100644 --- a/lib/ui/navigation_drawer.dart +++ b/lib/ui/navigation_drawer.dart @@ -16,10 +16,10 @@ class NavigationDrawer extends StatelessWidget { final bool isInDrawer; const NavigationDrawer({ - Key key, - this.selectedIndex, - this.onTap, - this.entries, + Key? key, + required this.selectedIndex, + required this.onTap, + required this.entries, this.isInDrawer = true, }) : super(key: key); @@ -81,10 +81,10 @@ class NavigationDrawer extends StatelessWidget { Widget _createDrawerItem( BuildContext context, { - Widget icon, - String text, - bool isSelected, - int index, + required Widget icon, + required String text, + required bool isSelected, + required int index, }) { return Padding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), @@ -169,7 +169,7 @@ class NavigationDrawer extends StatelessWidget { } class DrawerNavigationEntry { - final Widget icon; + final Icon icon; final String title; DrawerNavigationEntry(this.icon, this.title); diff --git a/lib/ui/onboarding/onboardin_step.dart b/lib/ui/onboarding/onboardin_step.dart index 37ee31aa..c800089f 100644 --- a/lib/ui/onboarding/onboardin_step.dart +++ b/lib/ui/onboarding/onboardin_step.dart @@ -17,7 +17,7 @@ abstract class OnboardingStep { OnboardingStepViewModel viewModel(); - String nextStep(); + String? nextStep(); } class SelectSourceOnboardingStep extends OnboardingStep { @@ -31,7 +31,7 @@ class SelectSourceOnboardingStep extends OnboardingStep { } @override - String nextStep() { + String? nextStep() { return _viewModel.nextStep(); } @@ -53,7 +53,7 @@ class DualisCredentialsOnboardingStep extends OnboardingStep { } @override - String nextStep() { + String? nextStep() { return null; } @@ -108,7 +108,7 @@ class IcalOnboardingStep extends OnboardingStep { } class MannheimOnboardingStep extends OnboardingStep { - MannheimViewModel _viewModel; + MannheimViewModel? _viewModel; @override Widget buildContent(BuildContext context) { @@ -122,12 +122,8 @@ class MannheimOnboardingStep extends OnboardingStep { @override OnboardingStepViewModel viewModel() { - if (_viewModel == null) { - _viewModel = MannheimViewModel( - KiwiContainer().resolve(), - ); - } - - return _viewModel; + return _viewModel ??= MannheimViewModel( + KiwiContainer().resolve(), + ); } } diff --git a/lib/ui/onboarding/onboarding_page.dart b/lib/ui/onboarding/onboarding_page.dart index 58a9f086..651f2bbe 100644 --- a/lib/ui/onboarding/onboarding_page.dart +++ b/lib/ui/onboarding/onboarding_page.dart @@ -1,5 +1,4 @@ import 'package:animations/animations.dart'; -import 'package:dhbwstudentapp/common/ui/viewmodels/base_view_model.dart'; import 'package:dhbwstudentapp/common/ui/viewmodels/root_view_model.dart'; import 'package:dhbwstudentapp/ui/onboarding/viewmodels/onboarding_view_model.dart'; import 'package:dhbwstudentapp/ui/onboarding/viewmodels/onboarding_view_model_base.dart'; @@ -10,7 +9,7 @@ import 'package:kiwi/kiwi.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; class OnboardingPage extends StatefulWidget { - const OnboardingPage({Key key}) : super(key: key); + const OnboardingPage({Key? key}) : super(key: key); @override _OnboardingPageState createState() => _OnboardingPageState(); @@ -18,8 +17,8 @@ class OnboardingPage extends StatefulWidget { class _OnboardingPageState extends State with TickerProviderStateMixin { - AnimationController _controller; - OnboardingViewModel viewModel; + late AnimationController _controller; + late OnboardingViewModel viewModel; _OnboardingPageState(); @@ -35,7 +34,7 @@ class _OnboardingPageState extends State viewModel.addListener( () async { await _controller.animateTo( - viewModel.stepIndex / viewModel.onboardingSteps, + viewModel.stepIndex! / viewModel.onboardingSteps, curve: Curves.ease, duration: const Duration(milliseconds: 300)); }, @@ -75,12 +74,12 @@ class _OnboardingPageState extends State child: Padding( padding: const EdgeInsets.fromLTRB(0, 120, 0, 90), child: PropertyChangeConsumer( - builder: (BuildContext context, BaseViewModel model, _) { + builder: (BuildContext context, OnboardingViewModel? model, _) { return Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ - Expanded(child: _buildActiveOnboardingPage(model)), + Expanded(child: _buildActiveOnboardingPage(model!)), OnboardingButtonBar( onPrevious: () { _navigateBack(context); @@ -101,7 +100,7 @@ class _OnboardingPageState extends State } Widget _buildActiveOnboardingPage(OnboardingViewModel model) { - var currentStep = model.pages[model.currentStep]; + var currentStep = model.pages[model.currentStep]!; var contentWidget = currentStep.buildContent(context); Widget body = Padding( @@ -109,13 +108,11 @@ class _OnboardingPageState extends State child: contentWidget, ); - if (currentStep != null) { - body = PropertyChangeProvider( - key: ValueKey(model.currentStep), - value: currentStep.viewModel(), - child: body, - ); - } + body = PropertyChangeProvider( + key: ValueKey(model.currentStep), + value: currentStep.viewModel(), + child: body, + ); return IntrinsicHeight( child: PageTransitionSwitcher( @@ -146,7 +143,8 @@ class _OnboardingPageState extends State } void _onboardingFinished() { - var rootViewModel = PropertyChangeProvider.of(context).value; + var rootViewModel = + PropertyChangeProvider.of(context)!.value; rootViewModel.setIsOnboarding(false); Navigator.of(context).pushReplacementNamed("main"); diff --git a/lib/ui/onboarding/viewmodels/dualis_login_view_model.dart b/lib/ui/onboarding/viewmodels/dualis_login_view_model.dart index d040074b..25e74c15 100644 --- a/lib/ui/onboarding/viewmodels/dualis_login_view_model.dart +++ b/lib/ui/onboarding/viewmodels/dualis_login_view_model.dart @@ -7,8 +7,7 @@ class DualisLoginViewModel extends OnboardingStepViewModel { final PreferencesProvider preferencesProvider; final DualisService dualisService; - String username; - String password; + Credentials? credentials; bool _isLoading = false; bool get isLoading => _isLoading; @@ -16,27 +15,28 @@ class DualisLoginViewModel extends OnboardingStepViewModel { bool _loginSuccess = false; bool get loginSuccess => _loginSuccess; - bool _passwordOrUsernameWrong = false; - bool get passwordOrUsernameWrong => _passwordOrUsernameWrong; - DualisLoginViewModel(this.preferencesProvider, this.dualisService); - Future testCredentials(String username, String password) async { - this.username = username; - this.password = password; + @override + void setIsValid(bool isValid) { + if (credentials == null) { + super.setIsValid(false); + } else { + super.setIsValid(isValid); + } + } + Future testCredentials(Credentials credentials) async { try { _isLoading = true; notifyListeners("isLoading"); _loginSuccess = - await dualisService.login(username, password) == LoginResult.LoggedIn; - _passwordOrUsernameWrong = !_loginSuccess; + await dualisService.login(credentials) == LoginResult.LoggedIn; setIsValid(_loginSuccess); } catch (ex) { setIsValid(false); - _passwordOrUsernameWrong = true; } finally { _isLoading = false; } @@ -46,13 +46,12 @@ class DualisLoginViewModel extends OnboardingStepViewModel { @override Future save() async { + if (!isValid) return; + await preferencesProvider.setStoreDualisCredentials(true); // To improve the onboarding experience we do not await this call because // it may take a few seconds - preferencesProvider.storeDualisCredentials(Credentials( - username, - password, - )); + preferencesProvider.storeDualisCredentials(credentials!); } } diff --git a/lib/ui/onboarding/viewmodels/ical_url_view_model.dart b/lib/ui/onboarding/viewmodels/ical_url_view_model.dart index 78ed03e6..346f6648 100644 --- a/lib/ui/onboarding/viewmodels/ical_url_view_model.dart +++ b/lib/ui/onboarding/viewmodels/ical_url_view_model.dart @@ -8,14 +8,14 @@ class IcalUrlViewModel extends OnboardingStepViewModel { final PreferencesProvider preferencesProvider; final ScheduleSourceProvider scheduleSourceProvider; - String _url; - String get url => _url; + String? _url; + String? get url => _url; bool urlHasError = false; IcalUrlViewModel(this.preferencesProvider, this.scheduleSourceProvider); - void setUrl(String url) { + void setUrl(String? url) { _url = url; notifyListeners("url"); @@ -24,7 +24,7 @@ class IcalUrlViewModel extends OnboardingStepViewModel { } void _validateUrl() { - urlHasError = !IcalScheduleSource.isValidUrl(_url); + urlHasError = !IcalScheduleSource.isValidUrl(_url!); setIsValid(!urlHasError); @@ -32,10 +32,10 @@ class IcalUrlViewModel extends OnboardingStepViewModel { } Future pasteUrl() async { - ClipboardData data = await Clipboard.getData('text/plain'); + ClipboardData? data = await Clipboard.getData('text/plain'); if (data?.text != null) { - setUrl(data.text); + setUrl(data!.text); } } diff --git a/lib/ui/onboarding/viewmodels/mannheim_view_model.dart b/lib/ui/onboarding/viewmodels/mannheim_view_model.dart index 757125fa..3ae7a468 100644 --- a/lib/ui/onboarding/viewmodels/mannheim_view_model.dart +++ b/lib/ui/onboarding/viewmodels/mannheim_view_model.dart @@ -14,11 +14,11 @@ class MannheimViewModel extends OnboardingStepViewModel { LoadCoursesState _loadingState = LoadCoursesState.Loading; LoadCoursesState get loadingState => _loadingState; - Course _selectedCourse; - Course get selectedCourse => _selectedCourse; + Course? _selectedCourse; + Course? get selectedCourse => _selectedCourse; - List _courses; - List get courses => _courses; + List? _courses; + List? get courses => _courses; MannheimViewModel(this._scheduleSourceProvider) { setIsValid(false); diff --git a/lib/ui/onboarding/viewmodels/onboarding_view_model.dart b/lib/ui/onboarding/viewmodels/onboarding_view_model.dart index 9128e541..b9749d75 100644 --- a/lib/ui/onboarding/viewmodels/onboarding_view_model.dart +++ b/lib/ui/onboarding/viewmodels/onboarding_view_model.dart @@ -28,11 +28,11 @@ class OnboardingViewModel extends BaseViewModel { final Map stepsBackstack = {}; int _stepIndex = 0; - int get stepIndex => _stepIndex; + int? get stepIndex => _stepIndex; String get currentStep => steps[_stepIndex]; - bool get currentPageValid => pages[currentStep].viewModel().isValid; + bool get currentPageValid => pages[currentStep]!.viewModel().isValid; bool get isLastStep => _stepIndex >= steps.length - 1; get onboardingSteps => steps.length; @@ -61,7 +61,7 @@ class OnboardingViewModel extends BaseViewModel { var lastPage = stepsBackstack.keys.last; - _stepIndex = stepsBackstack[lastPage]; + _stepIndex = stepsBackstack[lastPage]!; stepsBackstack.remove(lastPage); @@ -77,7 +77,7 @@ class OnboardingViewModel extends BaseViewModel { return; } - var nextDesiredStep = pages[currentStep].nextStep(); + var nextDesiredStep = pages[currentStep]!.nextStep(); stepsBackstack[currentStep] = _stepIndex; @@ -96,10 +96,10 @@ class OnboardingViewModel extends BaseViewModel { Future finishOnboarding() async { for (var step in stepsBackstack.keys) { - await pages[step].viewModel().save(); + await pages[step]!.viewModel().save(); } - _onboardingFinished?.call(); + _onboardingFinished.call(); await analytics.logTutorialComplete(); await analytics.setUserProperty(name: "onboarding_finished", value: "true"); diff --git a/lib/ui/onboarding/viewmodels/onboarding_view_model_base.dart b/lib/ui/onboarding/viewmodels/onboarding_view_model_base.dart index 26ebd7da..fba1ca43 100644 --- a/lib/ui/onboarding/viewmodels/onboarding_view_model_base.dart +++ b/lib/ui/onboarding/viewmodels/onboarding_view_model_base.dart @@ -4,6 +4,7 @@ abstract class OnboardingStepViewModel extends BaseViewModel { bool _isValid = false; bool get isValid => _isValid; + // TODO: [Leptopoda] kinda redundant now that we have null safety?¿ void setIsValid(bool isValid) { _isValid = isValid; notifyListeners("isValid"); diff --git a/lib/ui/onboarding/viewmodels/rapla_url_view_model.dart b/lib/ui/onboarding/viewmodels/rapla_url_view_model.dart index 76fbf4c1..6511643b 100644 --- a/lib/ui/onboarding/viewmodels/rapla_url_view_model.dart +++ b/lib/ui/onboarding/viewmodels/rapla_url_view_model.dart @@ -8,8 +8,8 @@ class RaplaUrlViewModel extends OnboardingStepViewModel { final PreferencesProvider preferencesProvider; final ScheduleSourceProvider scheduleSourceProvider; - String _raplaUrl; - String get raplaUrl => _raplaUrl; + String? _raplaUrl; + String? get raplaUrl => _raplaUrl; bool urlHasError = false; @@ -18,7 +18,7 @@ class RaplaUrlViewModel extends OnboardingStepViewModel { this.scheduleSourceProvider, ); - void setRaplaUrl(String url) { + void setRaplaUrl(String? url) { _raplaUrl = url; notifyListeners("raplaUrl"); @@ -27,7 +27,7 @@ class RaplaUrlViewModel extends OnboardingStepViewModel { } void _validateUrl() { - urlHasError = !RaplaScheduleSource.isValidUrl(_raplaUrl); + urlHasError = !RaplaScheduleSource.isValidUrl(_raplaUrl!); setIsValid(!urlHasError); @@ -35,10 +35,10 @@ class RaplaUrlViewModel extends OnboardingStepViewModel { } Future pasteUrl() async { - ClipboardData data = await Clipboard.getData('text/plain'); + ClipboardData? data = await Clipboard.getData('text/plain'); if (data?.text != null) { - setRaplaUrl(data.text); + setRaplaUrl(data!.text); } } diff --git a/lib/ui/onboarding/viewmodels/select_source_view_model.dart b/lib/ui/onboarding/viewmodels/select_source_view_model.dart index ec95f6fa..8310a6f3 100644 --- a/lib/ui/onboarding/viewmodels/select_source_view_model.dart +++ b/lib/ui/onboarding/viewmodels/select_source_view_model.dart @@ -12,9 +12,11 @@ class SelectSourceViewModel extends OnboardingStepViewModel { setIsValid(true); } - void setScheduleSourceType(ScheduleSourceType type) { + void setScheduleSourceType(ScheduleSourceType? type) { + if (type == null) return; + _scheduleSourceType = type; - setIsValid(_scheduleSourceType != null); + setIsValid(true); notifyListeners("scheduleSourceType"); } @@ -24,7 +26,7 @@ class SelectSourceViewModel extends OnboardingStepViewModel { await _preferencesProvider.setScheduleSourceType(scheduleSourceType.index); } - String nextStep() { + String? nextStep() { switch (_scheduleSourceType) { case ScheduleSourceType.Rapla: return "rapla"; @@ -36,8 +38,8 @@ class SelectSourceViewModel extends OnboardingStepViewModel { return "mannheim"; case ScheduleSourceType.Ical: return "ical"; + default: + return null; } - - return null; } } diff --git a/lib/ui/onboarding/widgets/dualis_login_page.dart b/lib/ui/onboarding/widgets/dualis_login_page.dart index 52630741..f4faf068 100644 --- a/lib/ui/onboarding/widgets/dualis_login_page.dart +++ b/lib/ui/onboarding/widgets/dualis_login_page.dart @@ -1,4 +1,5 @@ import 'package:dhbwstudentapp/common/i18n/localizations.dart'; +import 'package:dhbwstudentapp/dualis/model/credentials.dart'; import 'package:dhbwstudentapp/ui/login_credentials_widget.dart'; import 'package:dhbwstudentapp/ui/onboarding/viewmodels/dualis_login_view_model.dart'; import 'package:dhbwstudentapp/ui/onboarding/viewmodels/onboarding_view_model_base.dart'; @@ -10,6 +11,8 @@ import 'package:property_change_notifier/property_change_notifier.dart'; /// credentials. /// class DualisLoginCredentialsPage extends StatefulWidget { + const DualisLoginCredentialsPage({Key? key}) : super(key: key); + @override _DualisLoginCredentialsPageState createState() => _DualisLoginCredentialsPageState(); @@ -17,31 +20,27 @@ class DualisLoginCredentialsPage extends StatefulWidget { class _DualisLoginCredentialsPageState extends State { - final TextEditingController _usernameEditingController = - TextEditingController(); - final TextEditingController _passwordEditingController = - TextEditingController(); + final CredentialsEditingController _controller = + CredentialsEditingController(); @override Widget build(BuildContext context) { return PropertyChangeConsumer( - builder: - (BuildContext context, OnboardingStepViewModel base, Set _) { - var viewModel = base as DualisLoginViewModel; + builder: (BuildContext context, OnboardingStepViewModel? base, + Set? _) { + final viewModel = base as DualisLoginViewModel?; - if (_usernameEditingController.text != viewModel.username) { - _usernameEditingController.text = viewModel.username; - } - if (_passwordEditingController.text != viewModel.password) { - _passwordEditingController.text = viewModel.password; + final credentials = viewModel?.credentials; + + if (credentials != null) { + _controller.credentials = credentials; } var widgets = []; widgets.addAll(_buildHeader()); widgets.add(LoginCredentialsWidget( - usernameEditingController: _usernameEditingController, - passwordEditingController: _passwordEditingController, + controller: _controller, onSubmitted: () async { await _testCredentials(viewModel); }, @@ -57,7 +56,7 @@ class _DualisLoginCredentialsPageState ); } - Widget _buildTestCredentialsButton(DualisLoginViewModel viewModel) { + Widget _buildTestCredentialsButton(DualisLoginViewModel? viewModel) { return Expanded( child: SizedBox( height: 64, @@ -69,7 +68,7 @@ class _DualisLoginCredentialsPageState crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: viewModel.passwordOrUsernameWrong + child: viewModel?.isValid ?? false ? Text( L.of(context).onboardingDualisWrongCredentials, textAlign: TextAlign.start, @@ -77,7 +76,7 @@ class _DualisLoginCredentialsPageState ) : Container(), ), - viewModel.isLoading + viewModel?.isLoading ?? false ? const SizedBox( width: 16, height: 16, @@ -85,7 +84,7 @@ class _DualisLoginCredentialsPageState strokeWidth: 1, ), ) - : viewModel.loginSuccess + : viewModel?.loginSuccess ?? false ? const Icon( Icons.check, color: Colors.green, @@ -106,11 +105,10 @@ class _DualisLoginCredentialsPageState ); } - Future _testCredentials(DualisLoginViewModel viewModel) async { - await viewModel.testCredentials( - _usernameEditingController.text, - _passwordEditingController.text, - ); + Future _testCredentials(DualisLoginViewModel? viewModel) async { + if (viewModel == null) return; + + await viewModel.testCredentials(_controller.credentials); } List _buildHeader() { diff --git a/lib/ui/onboarding/widgets/ical_url_page.dart b/lib/ui/onboarding/widgets/ical_url_page.dart index dfd690bc..cdb6316f 100644 --- a/lib/ui/onboarding/widgets/ical_url_page.dart +++ b/lib/ui/onboarding/widgets/ical_url_page.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; class IcalUrlPage extends StatefulWidget { + const IcalUrlPage({Key? key}) : super(key: key); + @override _IcalUrlPageState createState() => _IcalUrlPageState(); } @@ -40,12 +42,13 @@ class _IcalUrlPageState extends State { child: Padding( padding: const EdgeInsets.fromLTRB(0, 32, 0, 0), child: PropertyChangeConsumer( - builder: (BuildContext context, OnboardingStepViewModel model, - Set _) { - var viewModel = model as IcalUrlViewModel; + builder: (BuildContext context, OnboardingStepViewModel? model, + Set? _) { + final viewModel = model as IcalUrlViewModel?; - if (_urlTextController.text != viewModel.url) - _urlTextController.text = viewModel.url; + if (viewModel?.url != null && + _urlTextController.text != viewModel!.url) + _urlTextController.text = viewModel.url!; return Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 16), @@ -56,19 +59,17 @@ class _IcalUrlPageState extends State { child: TextField( controller: _urlTextController, decoration: InputDecoration( - errorText: (viewModel.urlHasError ?? false) + errorText: (viewModel?.urlHasError == true) ? L.of(context).onboardingRaplaUrlInvalid : null, hintText: L.of(context).onboardingIcalUrlHint, ), - onChanged: (value) { - viewModel.setUrl(value); - }, + onChanged: viewModel?.setUrl, ), ), TextButton.icon( onPressed: () async { - await viewModel.pasteUrl(); + await viewModel?.pasteUrl(); }, icon: Icon(Icons.content_paste), label: Text( diff --git a/lib/ui/onboarding/widgets/mannheim_page.dart b/lib/ui/onboarding/widgets/mannheim_page.dart index 83495596..ff9c0890 100644 --- a/lib/ui/onboarding/widgets/mannheim_page.dart +++ b/lib/ui/onboarding/widgets/mannheim_page.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; class MannheimPage extends StatefulWidget { + const MannheimPage({Key? key}) : super(key: key); + @override _MannheimPageState createState() => _MannheimPageState(); } @@ -50,20 +52,19 @@ class SelectMannheimCourseWidget extends StatelessWidget { @override Widget build(BuildContext context) { return PropertyChangeConsumer( - builder: - (BuildContext context, OnboardingStepViewModel model, Set _) { - var viewModel = model as MannheimViewModel; + builder: (BuildContext context, OnboardingStepViewModel? model, + Set? _) { + final viewModel = model as MannheimViewModel?; - switch (viewModel.loadingState) { + switch (viewModel?.loadingState) { case LoadCoursesState.Loading: return _buildLoadingIndicator(); case LoadCoursesState.Loaded: - return _buildCourseList(context, viewModel); + return _buildCourseList(context, viewModel!); case LoadCoursesState.Failed: + default: return _buildLoadingError(context, viewModel); } - - return Container(); }, ); } @@ -89,7 +90,8 @@ class SelectMannheimCourseWidget extends StatelessWidget { int index, BuildContext context, ) { - var isSelected = viewModel.selectedCourse == viewModel.courses[index]; + // TODO: [Leptopoda] why is nullsafety garanttueed here but checked above ¿? + var isSelected = viewModel.selectedCourse == viewModel.courses![index]; return ListTile( trailing: isSelected @@ -99,19 +101,20 @@ class SelectMannheimCourseWidget extends StatelessWidget { ) : null, title: Text( - viewModel.courses[index].name, + viewModel.courses![index].name, style: isSelected ? TextStyle( color: Theme.of(context).colorScheme.secondary, ) : null, ), - subtitle: Text(viewModel.courses[index].title), - onTap: () => viewModel.setSelectedCourse(viewModel.courses[index]), + subtitle: Text(viewModel.courses![index].title), + onTap: () => viewModel.setSelectedCourse(viewModel.courses![index]), ); } - Widget _buildLoadingError(BuildContext context, MannheimViewModel viewModel) { + Widget _buildLoadingError( + BuildContext context, MannheimViewModel? viewModel) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -120,7 +123,7 @@ class SelectMannheimCourseWidget extends StatelessWidget { Padding( padding: const EdgeInsets.all(16), child: MaterialButton( - onPressed: viewModel.loadCourses, + onPressed: viewModel?.loadCourses, child: Icon(Icons.refresh), ), ), diff --git a/lib/ui/onboarding/widgets/onboarding_button_bar.dart b/lib/ui/onboarding/widgets/onboarding_button_bar.dart index 41f4deb6..f95cc7ab 100644 --- a/lib/ui/onboarding/widgets/onboarding_button_bar.dart +++ b/lib/ui/onboarding/widgets/onboarding_button_bar.dart @@ -4,14 +4,14 @@ import 'package:flutter/material.dart'; class OnboardingButtonBar extends StatelessWidget { final OnboardingViewModel viewModel; - final Function onNext; - final Function onPrevious; + final VoidCallback onNext; + final VoidCallback onPrevious; const OnboardingButtonBar({ - Key key, - @required this.viewModel, - @required this.onNext, - @required this.onPrevious, + Key? key, + required this.viewModel, + required this.onNext, + required this.onPrevious, }) : super(key: key); @override diff --git a/lib/ui/onboarding/widgets/onboarding_page_background.dart b/lib/ui/onboarding/widgets/onboarding_page_background.dart index 715b0747..b31b0242 100644 --- a/lib/ui/onboarding/widgets/onboarding_page_background.dart +++ b/lib/ui/onboarding/widgets/onboarding_page_background.dart @@ -20,7 +20,7 @@ class OnboardingPageBackground extends StatelessWidget { Brightness.dark: "assets/onboarding_bottom_background_dark.png", }; - OnboardingPageBackground({Key key, this.controller}) + OnboardingPageBackground({Key? key, required this.controller}) : angleTopBackground = Tween( begin: PlatformUtil.isPhone() ? -32 : -10, end: PlatformUtil.isPhone() ? -10 : -5, @@ -75,7 +75,7 @@ class OnboardingPageBackground extends StatelessWidget { ), super(key: key); - Widget _buildAnimation(BuildContext context, Widget child) { + Widget _buildAnimation(BuildContext context, Widget? child) { return Stack( children: [ Transform.rotate( @@ -111,7 +111,7 @@ class OnboardingPageBackground extends StatelessWidget { child: Transform.scale( scale: 1.5, child: Image.asset( - background[Theme.of(context).brightness], + background[Theme.of(context).brightness]!, ), ), ), @@ -129,7 +129,7 @@ class OnboardingPageBackground extends StatelessWidget { child: Transform.scale( scale: 1.5, child: Image.asset( - foreground[Theme.of(context).brightness], + foreground[Theme.of(context).brightness]!, ), ), ), diff --git a/lib/ui/onboarding/widgets/rapla_url_page.dart b/lib/ui/onboarding/widgets/rapla_url_page.dart index 2e5b5f3e..512b5d12 100644 --- a/lib/ui/onboarding/widgets/rapla_url_page.dart +++ b/lib/ui/onboarding/widgets/rapla_url_page.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; class RaplaUrlPage extends StatefulWidget { + const RaplaUrlPage({Key? key}) : super(key: key); + @override _RaplaUrlPageState createState() => _RaplaUrlPageState(); } @@ -40,12 +42,13 @@ class _RaplaUrlPageState extends State { child: Padding( padding: const EdgeInsets.fromLTRB(0, 32, 0, 0), child: PropertyChangeConsumer( - builder: (BuildContext context, OnboardingStepViewModel model, - Set _) { - var viewModel = model as RaplaUrlViewModel; + builder: (BuildContext context, OnboardingStepViewModel? model, + Set? _) { + final viewModel = model as RaplaUrlViewModel?; - if (_urlTextController.text != viewModel.raplaUrl) - _urlTextController.text = viewModel.raplaUrl; + if (viewModel?.raplaUrl != null && + _urlTextController.text != viewModel!.raplaUrl) + _urlTextController.text = viewModel.raplaUrl!; return Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 16), @@ -56,19 +59,17 @@ class _RaplaUrlPageState extends State { child: TextField( controller: _urlTextController, decoration: InputDecoration( - errorText: (viewModel.urlHasError ?? false) + errorText: viewModel?.urlHasError == true ? L.of(context).onboardingRaplaUrlInvalid : null, hintText: L.of(context).onboardingRaplaUrlHint, ), - onChanged: (value) { - viewModel.setRaplaUrl(value); - }, + onChanged: viewModel?.setRaplaUrl, ), ), TextButton.icon( onPressed: () async { - await viewModel.pasteUrl(); + await viewModel?.pasteUrl(); }, icon: Icon(Icons.content_paste), label: Text( diff --git a/lib/ui/onboarding/widgets/select_source_page.dart b/lib/ui/onboarding/widgets/select_source_page.dart index e10eb9e5..b420a366 100644 --- a/lib/ui/onboarding/widgets/select_source_page.dart +++ b/lib/ui/onboarding/widgets/select_source_page.dart @@ -6,17 +6,17 @@ import 'package:flutter/material.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; class SelectSourcePage extends StatelessWidget { - const SelectSourcePage({Key key}) : super(key: key); + const SelectSourcePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return PropertyChangeConsumer( builder: ( BuildContext context, - OnboardingStepViewModel model, - Set _, + OnboardingStepViewModel? model, + Set? _, ) { - var viewModel = model as SelectSourceViewModel; + final viewModel = model as SelectSourceViewModel?; return Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -92,15 +92,15 @@ class SelectSourcePage extends StatelessWidget { } RadioListTile buildScheduleTypeRadio( - SelectSourceViewModel viewModel, + SelectSourceViewModel? viewModel, BuildContext context, ScheduleSourceType type, String title) { return RadioListTile( value: type, //model.useDualis, - onChanged: viewModel.setScheduleSourceType, + onChanged: viewModel?.setScheduleSourceType, title: Text(title), - groupValue: viewModel.scheduleSourceType, + groupValue: viewModel?.scheduleSourceType, ); } } diff --git a/lib/ui/pager_widget.dart b/lib/ui/pager_widget.dart index 96cbc68f..7d2dc3ed 100644 --- a/lib/ui/pager_widget.dart +++ b/lib/ui/pager_widget.dart @@ -15,24 +15,20 @@ import 'package:provider/provider.dart'; /// class PagerWidget extends StatefulWidget { final List pages; - final String pagesId; + final String? pagesId; - const PagerWidget({Key key, @required this.pages, this.pagesId}) + const PagerWidget({Key? key, required this.pages, this.pagesId}) : super(key: key); @override - _PagerWidgetState createState() => _PagerWidgetState(pages, pagesId); + _PagerWidgetState createState() => _PagerWidgetState(); } class _PagerWidgetState extends State { final PreferencesProvider preferencesProvider = KiwiContainer().resolve(); - final String pagesId; - final List pages; int _currentPage = 0; - _PagerWidgetState(this.pages, this.pagesId); - @override void initState() { super.initState(); @@ -49,37 +45,23 @@ class _PagerWidgetState extends State { key: ValueKey(_currentPage), children: [ Expanded( - child: _wrapWithChangeNotifierProvider( - pages[_currentPage].builder(context), - pages[_currentPage].viewModel, - ), + child: widget.pages[_currentPage].widget(context), ), ], ), ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentPage, - onTap: (int index) async { - await setActivePage(index); - }, + onTap: setActivePage, items: buildBottomNavigationBarItems(), ), ); } - Widget _wrapWithChangeNotifierProvider(Widget child, BaseViewModel value) { - if (value == null) return child; - - return ChangeNotifierProvider.value( - child: child, - value: value, - ); - } - List buildBottomNavigationBarItems() { var bottomNavigationBarItems = []; - for (var page in pages) { + for (var page in widget.pages) { bottomNavigationBarItems.add( BottomNavigationBarItem( icon: page.icon, @@ -91,7 +73,7 @@ class _PagerWidgetState extends State { } Future setActivePage(int page) async { - if (page < 0 || page >= pages.length) { + if (page < 0 || page >= widget.pages.length) { return; } @@ -99,20 +81,20 @@ class _PagerWidgetState extends State { _currentPage = page; }); - if (pagesId == null) return; - await preferencesProvider.set("${pagesId}_active_page", page); + if (widget.pagesId == null) return; + await preferencesProvider.set("${widget.pagesId}_active_page", page); } Future loadActivePage() async { - if (pagesId == null) return; + if (widget.pagesId == null) return; var selectedPage = await preferencesProvider.get( - "${pagesId}_active_page", + "${widget.pagesId}_active_page", ); if (selectedPage == null) return; - if (selectedPage > 0 && selectedPage < pages.length) { + if (selectedPage > 0 && selectedPage < widget.pages.length) { setState(() { _currentPage = selectedPage; }); @@ -120,16 +102,26 @@ class _PagerWidgetState extends State { } } -class PageDefinition { +class PageDefinition { final Widget icon; final String text; final WidgetBuilder builder; - final BaseViewModel viewModel; + final T? viewModel; PageDefinition({ - @required this.icon, - @required this.text, - @required this.builder, + required this.icon, + required this.text, + required this.builder, this.viewModel, }); + + /// Wraps the Widget with a [ChangeNotifierProvider] if [viewModel] is specified. + Widget widget(BuildContext context) { + if (viewModel == null) return builder(context); + + return ChangeNotifierProvider.value( + child: builder(context), + value: viewModel, + ); + } } diff --git a/lib/ui/root_page.dart b/lib/ui/root_page.dart index 82397d57..cb6c2027 100644 --- a/lib/ui/root_page.dart +++ b/lib/ui/root_page.dart @@ -16,17 +16,13 @@ import 'package:property_change_notifier/property_change_notifier.dart'; class RootPage extends StatefulWidget { final RootViewModel rootViewModel; - const RootPage({Key key, this.rootViewModel}) : super(key: key); + const RootPage({Key? key, required this.rootViewModel}) : super(key: key); @override - _RootPageState createState() => _RootPageState(rootViewModel); + _RootPageState createState() => _RootPageState(); } class _RootPageState extends State { - final RootViewModel rootViewModel; - - _RootPageState(this.rootViewModel); - @override void initState() { super.initState(); @@ -37,10 +33,12 @@ class _RootPageState extends State { return PropertyChangeProvider( child: PropertyChangeConsumer( properties: const ["appTheme", "isOnboarding"], - builder: (BuildContext context, RootViewModel model, Set properties) => - MaterialApp( - theme: ColorPalettes.buildTheme(model.appTheme), - initialRoute: rootViewModel.isOnboarding ? "onboarding" : "main", + builder: + (BuildContext context, RootViewModel? model, Set? properties) => + MaterialApp( + theme: ColorPalettes.buildTheme(model!.appTheme), + initialRoute: + widget.rootViewModel.isOnboarding ? "onboarding" : "main", navigatorKey: NavigatorKey.rootKey, navigatorObservers: [rootNavigationObserver], localizationsDelegates: [ @@ -57,7 +55,7 @@ class _RootPageState extends State { onGenerateRoute: generateRoute, ), ), - value: rootViewModel, + value: widget.rootViewModel, ); } } diff --git a/lib/ui/settings/donate_list_tile.dart b/lib/ui/settings/donate_list_tile.dart index 9562b533..4b17d511 100644 --- a/lib/ui/settings/donate_list_tile.dart +++ b/lib/ui/settings/donate_list_tile.dart @@ -7,6 +7,8 @@ import 'package:kiwi/kiwi.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; class DonateListTile extends StatefulWidget { + const DonateListTile({Key? key}) : super(key: key); + @override _DonateListTileState createState() => _DonateListTileState(); } @@ -14,7 +16,7 @@ class DonateListTile extends StatefulWidget { class _DonateListTileState extends State { final InAppPurchaseManager inAppPurchaseManager; - SettingsViewModel model; + late SettingsViewModel model; bool isPurchasing = false; @@ -34,7 +36,7 @@ class _DonateListTileState extends State { inAppPurchaseManager.removePurchaseCallback(null, purchaseCallback); } - void purchaseCallback(String productId, PurchaseResultEnum result) { + void purchaseCallback(String? productId, PurchaseResultEnum result) { if (!mounted) return; setState(() { @@ -49,7 +51,8 @@ class _DonateListTileState extends State { properties: const [ "didPurchaseWidget", ], - ).value; + )! + .value; if (model.widgetPurchaseState == null) { return Padding( @@ -91,8 +94,8 @@ class _DonateListTileState extends State { } void _purchaseClicked() async { - if (isPurchasing || model.widgetPurchaseState == PurchaseStateEnum.Purchased) - return; + if (isPurchasing || + model.widgetPurchaseState == PurchaseStateEnum.Purchased) return; setState(() { isPurchasing = true; diff --git a/lib/ui/settings/purchase_widget_list_tile.dart b/lib/ui/settings/purchase_widget_list_tile.dart index 8f8d9438..5cbd5594 100644 --- a/lib/ui/settings/purchase_widget_list_tile.dart +++ b/lib/ui/settings/purchase_widget_list_tile.dart @@ -7,13 +7,15 @@ import 'package:kiwi/kiwi.dart'; import 'package:property_change_notifier/property_change_notifier.dart'; class PurchaseWidgetListTile extends StatefulWidget { + const PurchaseWidgetListTile({Key? key}) : super(key: key); + @override _PurchaseWidgetListTileState createState() => _PurchaseWidgetListTileState(); } class _PurchaseWidgetListTileState extends State { final InAppPurchaseManager inAppPurchaseManager; - SettingsViewModel model; + late SettingsViewModel model; bool isPurchasing = false; @@ -34,7 +36,7 @@ class _PurchaseWidgetListTileState extends State { inAppPurchaseManager.removePurchaseCallback(null, purchaseCallback); } - void purchaseCallback(String productId, PurchaseResultEnum result) { + void purchaseCallback(String? productId, PurchaseResultEnum result) { if (!mounted) return; setState(() { @@ -50,9 +52,10 @@ class _PurchaseWidgetListTileState extends State { "didPurchaseWidget", "areWidgetsSupported", ], - ).value; + )! + .value; - if (!model.areWidgetsSupported || + if (!model.areWidgetsSupported! || model.widgetPurchaseState == PurchaseStateEnum.Unknown || model.widgetPurchaseState == null) { return Container(); @@ -66,7 +69,7 @@ class _PurchaseWidgetListTileState extends State { ); } - Widget _buildWidgetSubtitle() { + Widget? _buildWidgetSubtitle() { if (isPurchasing) { return Align( alignment: Alignment.centerLeft, diff --git a/lib/ui/settings/select_theme_dialog.dart b/lib/ui/settings/select_theme_dialog.dart index c6a337c1..340711b9 100644 --- a/lib/ui/settings/select_theme_dialog.dart +++ b/lib/ui/settings/select_theme_dialog.dart @@ -28,7 +28,8 @@ class SelectThemeDialog { properties: const [ "appTheme", ], - builder: (BuildContext context, RootViewModel model, Set properties) { + builder: + (BuildContext context, RootViewModel? model, Set? properties) { return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -36,19 +37,19 @@ class SelectThemeDialog { title: Text(L.of(context).selectThemeLight), value: AppTheme.Light, groupValue: _rootViewModel.appTheme, - onChanged: (v) => _rootViewModel.setAppTheme(v), + onChanged: _rootViewModel.setAppTheme, ), RadioListTile( title: Text(L.of(context).selectThemeDark), value: AppTheme.Dark, groupValue: _rootViewModel.appTheme, - onChanged: (v) => _rootViewModel.setAppTheme(v), + onChanged: _rootViewModel.setAppTheme, ), RadioListTile( title: Text(L.of(context).selectThemeSystem), value: AppTheme.System, groupValue: _rootViewModel.appTheme, - onChanged: (v) => _rootViewModel.setAppTheme(v), + onChanged: _rootViewModel.setAppTheme, ), ], ); diff --git a/lib/ui/settings/settings_page.dart b/lib/ui/settings/settings_page.dart index 7c7c173a..4d966bda 100644 --- a/lib/ui/settings/settings_page.dart +++ b/lib/ui/settings/settings_page.dart @@ -30,6 +30,8 @@ import 'package:url_launcher/url_launcher.dart'; /// of the app /// class SettingsPage extends StatefulWidget { + const SettingsPage({Key? key}) : super(key: key); + @override _SettingsPageState createState() => _SettingsPageState(); } @@ -128,10 +130,10 @@ class _SettingsPageState extends State { "prettifySchedule", ], builder: - (BuildContext context, SettingsViewModel model, Set properties) { + (BuildContext context, SettingsViewModel? model, Set? properties) { return SwitchListTile( title: Text(L.of(context).settingsPrettifySchedule), - onChanged: model.setPrettifySchedule, + onChanged: model!.setPrettifySchedule, value: model.prettifySchedule, ); }, @@ -162,7 +164,7 @@ class _SettingsPageState extends State { .isCalendarSyncEnabled(); List entriesToExport = KiwiContainer().resolve().listDateEntries; - await NavigatorKey.rootKey.currentState.push(MaterialPageRoute( + await NavigatorKey.rootKey.currentState!.push(MaterialPageRoute( builder: (BuildContext context) => CalendarExportPage( entriesToExport: entriesToExport, isCalendarSyncWidget: true, @@ -177,18 +179,18 @@ class _SettingsPageState extends State { List buildNotificationSettings(BuildContext context) { WorkSchedulerService service = KiwiContainer().resolve(); - if (service?.isSchedulingAvailable() ?? false) { + if (service.isSchedulingAvailable()) { return [ TitleListTile(title: L.of(context).settingsNotificationsTitle), PropertyChangeConsumer( properties: const [ "notifyAboutNextDay", ], - builder: - (BuildContext context, SettingsViewModel model, Set properties) { + builder: (BuildContext context, SettingsViewModel? model, + Set? properties) { return SwitchListTile( title: Text(L.of(context).settingsNotificationsNextDay), - onChanged: model.setNotifyAboutNextDay, + onChanged: model!.setNotifyAboutNextDay, value: model.notifyAboutNextDay, ); }, @@ -197,11 +199,11 @@ class _SettingsPageState extends State { properties: const [ "notifyAboutScheduleChanges", ], - builder: - (BuildContext context, SettingsViewModel model, Set properties) { + builder: (BuildContext context, SettingsViewModel? model, + Set? properties) { return SwitchListTile( title: Text(L.of(context).settingsNotificationsScheduleChange), - onChanged: model.setNotifyAboutScheduleChanges, + onChanged: model!.setNotifyAboutScheduleChanges, value: model.notifyAboutScheduleChanges, ); }, @@ -220,17 +222,17 @@ class _SettingsPageState extends State { properties: const [ "appTheme", ], - builder: (BuildContext context, RootViewModel model, Set properties) { + builder: (BuildContext context, RootViewModel? model, Set? properties) { return ListTile( title: Text(L.of(context).settingsDarkMode), onTap: () async { - await SelectThemeDialog(model).show(context); + await SelectThemeDialog(model!).show(context); }, subtitle: Text({ AppTheme.Dark: L.of(context).selectThemeDark, AppTheme.Light: L.of(context).selectThemeLight, AppTheme.System: L.of(context).selectThemeSystem, - }[model.appTheme]), + }[model!.appTheme]!), ); }, ), diff --git a/lib/ui/settings/viewmodels/settings_view_model.dart b/lib/ui/settings/viewmodels/settings_view_model.dart index 9db30d99..7bffb54c 100644 --- a/lib/ui/settings/viewmodels/settings_view_model.dart +++ b/lib/ui/settings/viewmodels/settings_view_model.dart @@ -34,20 +34,19 @@ class SettingsViewModel extends BaseViewModel { bool get isCalendarSyncEnabled => _isCalendarSyncEnabled; - PurchaseStateEnum _widgetPurchaseState; + PurchaseStateEnum? _widgetPurchaseState; - PurchaseStateEnum get widgetPurchaseState => _widgetPurchaseState; + PurchaseStateEnum? get widgetPurchaseState => _widgetPurchaseState; - bool _areWidgetsSupported = false; + bool? _areWidgetsSupported = false; - bool get areWidgetsSupported => _areWidgetsSupported; + bool? get areWidgetsSupported => _areWidgetsSupported; SettingsViewModel( - this._preferencesProvider, - this._nextDayInformationNotification, - this._widgetHelper, - this._inAppPurchaseManager - ) { + this._preferencesProvider, + this._nextDayInformationNotification, + this._widgetHelper, + this._inAppPurchaseManager) { _loadPreferences(); _inAppPurchaseManager.addPurchaseCallback( @@ -71,7 +70,7 @@ class SettingsViewModel extends BaseViewModel { ); } - void _widgetPurchaseCallback(String id, PurchaseResultEnum result) { + void _widgetPurchaseCallback(String? id, PurchaseResultEnum result) { if (result == PurchaseResultEnum.Success) { _widgetPurchaseState = PurchaseStateEnum.Purchased; } diff --git a/pubspec.lock b/pubspec.lock index f05222ab..7a882907 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + app_group_directory: + dependency: "direct main" + description: + name: app_group_directory + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" archive: dependency: transitive description: @@ -42,7 +49,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -56,7 +63,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -70,7 +77,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -120,13 +127,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.0" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -378,13 +392,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" - ios_app_group: - dependency: "direct main" - description: - name: ios_app_group - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" js: dependency: transitive description: @@ -426,21 +433,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -482,7 +489,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_provider: dependency: "direct main" description: @@ -697,7 +704,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" sprintf: dependency: transitive description: @@ -739,7 +746,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" synchronized: dependency: transitive description: @@ -753,28 +760,28 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: "direct dev" description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.21.1" + version: "1.21.4" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.13" + version: "0.4.16" timezone: dependency: "direct main" description: @@ -907,7 +914,7 @@ packages: name: workmanager url: "https://pub.dartlang.org" source: hosted - version: "0.4.1" + version: "0.5.0" xdg_directories: dependency: transitive description: @@ -930,5 +937,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=2.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 18ed687b..3f04227c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ description: An app for the DHBW Stuttgart version: 1.0.1+1 environment: - sdk: ">=2.10.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -35,7 +35,7 @@ dependencies: url_launcher: ^6.1.0 kiwi: ^4.0.2 flutter_local_notifications: ^9.4.1 # BREAKING CHANGES HERE! - workmanager: ^0.4.1 + workmanager: ^0.5.0 universal_html: ^2.0.8 firebase_core: ^1.15.0 firebase_analytics: ^9.1.6 @@ -48,8 +48,9 @@ dependencies: device_calendar: ^4.2.0 flutter_inapp_purchase: ^5.1.2 flutter_widgetkit: ^1.0.3 - ios_app_group: ^1.0.0 + app_group_directory: ^2.0.0 timezone: ^0.8.0 + equatable: ^2.0.0 dev_dependencies: flutter_test: @@ -57,13 +58,11 @@ dev_dependencies: test: any - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. @@ -81,6 +80,6 @@ flutter: - assets/schedule_empty_state_dark.png fonts: - - family: CustomIcons + - family: CustomIcons fonts: - asset: fonts/CustomIcons.ttf diff --git a/test/dualis/service/parsing/modules_from_course_result_page_extract_test.dart b/test/dualis/service/parsing/modules_from_course_result_page_extract_test.dart index 28bcc7d9..fd347f36 100644 --- a/test/dualis/service/parsing/modules_from_course_result_page_extract_test.dart +++ b/test/dualis/service/parsing/modules_from_course_result_page_extract_test.dart @@ -17,12 +17,12 @@ Future main() async { expect(modules.length, 3); - expect(modules[1].id, "T3INF1001"); - expect(modules[1].name, "Mathematik I"); - expect(modules[1].state, null); - expect(modules[1].credits, "8,0"); - expect(modules[1].finalGrade, "4,0"); - expect(modules[1].detailsUrl, + expect(modules[1]!.id, "T3INF1001"); + expect(modules[1]!.name, "Mathematik I"); + expect(modules[1]!.state, null); + expect(modules[1]!.credits, "8,0"); + expect(modules[1]!.finalGrade, "4,0"); + expect(modules[1]!.detailsUrl, "www.endpoint.com/scripts/mgrqispi.dll?APPNAME=CampusNet&PRGNAME=RESULTDETAILS&ARGUMENTS=-N123456789876543,-N000307,-N121212121212121"); }); diff --git a/test/schedule/service/rapla/rapla_response_parser_test.dart b/test/schedule/service/rapla/rapla_response_parser_test.dart index ced0b81d..deda5af1 100644 --- a/test/schedule/service/rapla/rapla_response_parser_test.dart +++ b/test/schedule/service/rapla/rapla_response_parser_test.dart @@ -114,7 +114,8 @@ Future main() async { expect(schedule.entries[0].end, DateTime(2021, 09, 22, 15, 00)); expect(schedule.entries[0].type, ScheduleEntryType.Class); expect(schedule.entries[0].professor, "A"); - expect(schedule.entries[0].room, "MOS-TINF19A,A 1.380 Vorlesungsraum (Mi 22.09.21 08:00, Do 23.09.21 08:00),A 1.390 Vorlesungsraum (Mi 22.09.21 08:00, Do 23.09.21 08:00)"); + expect(schedule.entries[0].room, + "MOS-TINF19A,A 1.380 Vorlesungsraum (Mi 22.09.21 08:00, Do 23.09.21 08:00),A 1.390 Vorlesungsraum (Mi 22.09.21 08:00, Do 23.09.21 08:00)"); expect(schedule.entries[2].title, "Tag der Deutschen Einheit"); expect(schedule.entries[2].start, DateTime(2021, 10, 03, 08, 00)); @@ -165,8 +166,7 @@ Future main() async { expect(schedule.entries.length, 20); }); - test('Rapla correctly read all classes of several months view 3', - () async { + test('Rapla correctly read all classes of several months view 3', () async { var parser = RaplaResponseParser(); var schedule = parser.parseSchedule(severalMonthsPage2).schedule; @@ -179,8 +179,7 @@ Future main() async { expect(schedule.entries.length, 36); }); - test('Rapla correctly read the day of a class in week view', - () async { + test('Rapla correctly read the day of a class in week view', () async { var parser = RaplaResponseParser(); var schedule = parser.parseSchedule(raplaPage1).schedule; @@ -190,55 +189,63 @@ Future main() async { expect(schedule.entries[0].end, DateTime(2021, 11, 02, 12, 15)); expect(schedule.entries[0].type, ScheduleEntryType.Class); expect(schedule.entries[0].professor, "Fr, Ta"); - expect(schedule.entries[0].room, "WDCM21B,G086 W Hörsaal (Di 26.10.21 13:00, Do 04.11.21 12:45, Di 16.11.21 10:00),A167 W Hörsaal (Di 30.11.21 10:00),F218_1 PA Hörsaal (Di 07.12.21 10:00, Mi 08.12.21 13:00),XOnline-Veranstaltung A Virtueller Raum (Di 02.11.21 09:00, Do 11.11.21 09:00)"); + expect(schedule.entries[0].room, + "WDCM21B,G086 W Hörsaal (Di 26.10.21 13:00, Do 04.11.21 12:45, Di 16.11.21 10:00),A167 W Hörsaal (Di 30.11.21 10:00),F218_1 PA Hörsaal (Di 07.12.21 10:00, Mi 08.12.21 13:00),XOnline-Veranstaltung A Virtueller Raum (Di 02.11.21 09:00, Do 11.11.21 09:00)"); - expect(schedule.entries[1].title, "Einführung in die Volkswirtschaftslehre und Mikroökonomik"); + expect(schedule.entries[1].title, + "Einführung in die Volkswirtschaftslehre und Mikroökonomik"); expect(schedule.entries[1].start, DateTime(2021, 11, 02, 13, 45)); expect(schedule.entries[1].end, DateTime(2021, 11, 02, 17, 00)); expect(schedule.entries[1].type, ScheduleEntryType.Class); expect(schedule.entries[1].professor, "Le, An"); - expect(schedule.entries[1].room, "WDCM21B,D221 W Hörsaal (Mo 11.10.21 09:00),G086 W Hörsaal (Mo 25.10.21 09:00, Mo 15.11.21 09:00),F218_1 PA Hörsaal (Mo 22.11.21 09:00, Mo 06.12.21 09:00),A167 W Hörsaal (Mo 29.11.21 09:00),XOnline-Veranstaltung A Virtueller Raum (Di 02.11.21 13:45)"); + expect(schedule.entries[1].room, + "WDCM21B,D221 W Hörsaal (Mo 11.10.21 09:00),G086 W Hörsaal (Mo 25.10.21 09:00, Mo 15.11.21 09:00),F218_1 PA Hörsaal (Mo 22.11.21 09:00, Mo 06.12.21 09:00),A167 W Hörsaal (Mo 29.11.21 09:00),XOnline-Veranstaltung A Virtueller Raum (Di 02.11.21 13:45)"); expect(schedule.entries[2].title, "Grundlagen des Bürgerlichen Rechts"); expect(schedule.entries[2].start, DateTime(2021, 11, 03, 09, 00)); expect(schedule.entries[2].end, DateTime(2021, 11, 03, 11, 30)); expect(schedule.entries[2].type, ScheduleEntryType.Class); expect(schedule.entries[2].professor, "Ei, An"); - expect(schedule.entries[2].room, "WDCM21B,XOnline-Veranstaltung A Virtueller Raum (Mi 13.10.21 09:00, Mi 27.10.21 09:00, Mi 10.11.21 09:00, Mi 24.11.21 09:00, Fr 03.12.21 10:00),D221 W Hörsaal (Mi 20.10.21 13:00, Mi 17.11.21 09:00),G086 W Hörsaal (Di 26.10.21 09:00, Mi 03.11.21 09:00),B354 W Hörsaal (Fr 03.12.21 10:00),F218_1 PA Hörsaal (Mi 08.12.21 09:00)"); + expect(schedule.entries[2].room, + "WDCM21B,XOnline-Veranstaltung A Virtueller Raum (Mi 13.10.21 09:00, Mi 27.10.21 09:00, Mi 10.11.21 09:00, Mi 24.11.21 09:00, Fr 03.12.21 10:00),D221 W Hörsaal (Mi 20.10.21 13:00, Mi 17.11.21 09:00),G086 W Hörsaal (Di 26.10.21 09:00, Mi 03.11.21 09:00),B354 W Hörsaal (Fr 03.12.21 10:00),F218_1 PA Hörsaal (Mi 08.12.21 09:00)"); expect(schedule.entries[3].title, "Technik der Finanzbuchführung I"); expect(schedule.entries[3].start, DateTime(2021, 11, 03, 13, 00)); expect(schedule.entries[3].end, DateTime(2021, 11, 03, 16, 15)); expect(schedule.entries[3].type, ScheduleEntryType.Class); expect(schedule.entries[3].professor, "Se, Ka"); - expect(schedule.entries[3].room, "WDCM21B,D221 W Hörsaal (Mi 17.11.21 13:00, Mi 06.10.21 13:00, Mi 13.10.21 13:00),G086 W Hörsaal (Mi 27.10.21 13:00, Mi 03.11.21 13:00, Mi 10.11.21 13:00),A167 W Hörsaal (Mi 24.11.21 13:00, Mi 01.12.21 14:00)"); + expect(schedule.entries[3].room, + "WDCM21B,D221 W Hörsaal (Mi 17.11.21 13:00, Mi 06.10.21 13:00, Mi 13.10.21 13:00),G086 W Hörsaal (Mi 27.10.21 13:00, Mi 03.11.21 13:00, Mi 10.11.21 13:00),A167 W Hörsaal (Mi 24.11.21 13:00, Mi 01.12.21 14:00)"); - expect(schedule.entries[4].title, "Grundlagen des wissenschaftlichen Arbeitens"); + expect(schedule.entries[4].title, + "Grundlagen des wissenschaftlichen Arbeitens"); expect(schedule.entries[4].start, DateTime(2021, 11, 04, 09, 00)); expect(schedule.entries[4].end, DateTime(2021, 11, 04, 12, 15)); expect(schedule.entries[4].type, ScheduleEntryType.Class); expect(schedule.entries[4].professor, "He, Be"); - expect(schedule.entries[4].room, "WDCM21B,D221 W Hörsaal (Di 05.10.21 09:00, Di 12.10.21 09:00),A167 W Hörsaal (Di 23.11.21 09:00),G086 W Hörsaal (Do 04.11.21 09:00)"); + expect(schedule.entries[4].room, + "WDCM21B,D221 W Hörsaal (Di 05.10.21 09:00, Di 12.10.21 09:00),A167 W Hörsaal (Di 23.11.21 09:00),G086 W Hörsaal (Do 04.11.21 09:00)"); expect(schedule.entries[5].title, "Grundlagen der Handelsbetriebslehre"); expect(schedule.entries[5].start, DateTime(2021, 11, 04, 12, 45)); expect(schedule.entries[5].end, DateTime(2021, 11, 04, 16, 00)); expect(schedule.entries[5].type, ScheduleEntryType.Class); expect(schedule.entries[5].professor, "Fr, Ta"); - expect(schedule.entries[5].room, "WDCM21B,G086 W Hörsaal (Di 26.10.21 13:00, Do 04.11.21 12:45, Di 16.11.21 10:00),A167 W Hörsaal (Di 30.11.21 10:00),F218_1 PA Hörsaal (Di 07.12.21 10:00, Mi 08.12.21 13:00),XOnline-Veranstaltung A Virtueller Raum (Di 02.11.21 09:00, Do 11.11.21 09:00)"); + expect(schedule.entries[5].room, + "WDCM21B,G086 W Hörsaal (Di 26.10.21 13:00, Do 04.11.21 12:45, Di 16.11.21 10:00),A167 W Hörsaal (Di 30.11.21 10:00),F218_1 PA Hörsaal (Di 07.12.21 10:00, Mi 08.12.21 13:00),XOnline-Veranstaltung A Virtueller Raum (Di 02.11.21 09:00, Do 11.11.21 09:00)"); expect(schedule.entries[6].title, "Einführung in die Programmierung"); expect(schedule.entries[6].start, DateTime(2021, 11, 05, 13, 00)); expect(schedule.entries[6].end, DateTime(2021, 11, 05, 16, 15)); expect(schedule.entries[6].type, ScheduleEntryType.Class); expect(schedule.entries[6].professor, "He, Ma"); - expect(schedule.entries[6].room, "WDCM21B,C348 PC Raum,D221 W Hörsaal (Fr 08.10.21 13:00, Fr 15.10.21 13:00, Fr 22.10.21 13:00),G086 W Hörsaal (Fr 29.10.21 13:00, Fr 05.11.21 13:00, Fr 12.11.21 13:00, Do 18.11.21 13:00),A167 W Hörsaal (Fr 26.11.21 13:00, Do 02.12.21 13:00)"); + expect(schedule.entries[6].room, + "WDCM21B,C348 PC Raum,D221 W Hörsaal (Fr 08.10.21 13:00, Fr 15.10.21 13:00, Fr 22.10.21 13:00),G086 W Hörsaal (Fr 29.10.21 13:00, Fr 05.11.21 13:00, Fr 12.11.21 13:00, Do 18.11.21 13:00),A167 W Hörsaal (Fr 26.11.21 13:00, Do 02.12.21 13:00)"); expect(schedule.entries.length, 7); }); - test('Rapla correctly read the week response', - () async { + test('Rapla correctly read the week response', () async { var parser = RaplaResponseParser(); var schedule = parser.parseSchedule(raplaWeekResponse).schedule; @@ -253,8 +260,7 @@ Future main() async { expect(schedule.entries.length, 6); }); - test('Rapla correctly read the week response 1', - () async { + test('Rapla correctly read the week response 1', () async { var parser = RaplaResponseParser(); var schedule = parser.parseSchedule(raplaWeekResponse1).schedule; @@ -264,7 +270,8 @@ Future main() async { expect(schedule.entries[0].end, DateTime(2021, 12, 13, 10, 00)); expect(schedule.entries[0].type, ScheduleEntryType.Exam); expect(schedule.entries[0].professor, "Man, R."); - expect(schedule.entries[0].room, "TEA20,H031, Hörsaal,N003, Hörsaal,N004, Hörsaal"); + expect(schedule.entries[0].room, + "TEA20,H031, Hörsaal,N003, Hörsaal,N004, Hörsaal"); expect(schedule.entries.length, 7); });