From a3f3b4cc840c81f306931449ce167a0ec4e205de Mon Sep 17 00:00:00 2001 From: Bruno Mendes Date: Mon, 2 Oct 2023 12:11:08 +0100 Subject: [PATCH] Bump packages, bump courses db version, further remove try/catch, fix forced logout --- .../background_callback.dart | 8 +- .../all_course_units_fetcher.dart | 18 ++--- .../controller/fetchers/profile_fetcher.dart | 35 ++++----- .../load_static/terms_and_conditions.dart | 24 ++---- .../local_storage/app_courses_database.dart | 2 +- .../local_storage/app_database.dart | 28 ++++--- .../local_storage/app_exams_database.dart | 2 +- .../controller/networking/network_router.dart | 73 ++++++++++--------- .../parsers/parser_course_units.dart | 2 +- uni/lib/main.dart | 25 ++++--- uni/lib/model/entities/course.dart | 4 +- uni/lib/model/entities/exam.dart | 6 -- uni/lib/model/entities/trip.dart | 7 -- .../providers/state_provider_notifier.dart | 59 +++++++++------ uni/lib/view/bug_report/widgets/form.dart | 16 ++-- .../view/common_widgets/page_transition.dart | 2 +- .../view/locations/widgets/faculty_map.dart | 6 +- uni/lib/view/login/login.dart | 6 +- uni/lib/view/navigation_service.dart | 19 ++--- uni/pubspec.yaml | 8 +- 20 files changed, 178 insertions(+), 172 deletions(-) diff --git a/uni/lib/controller/background_workers/background_callback.dart b/uni/lib/controller/background_workers/background_callback.dart index 9c9f45536..bf89d1e07 100644 --- a/uni/lib/controller/background_workers/background_callback.dart +++ b/uni/lib/controller/background_workers/background_callback.dart @@ -35,8 +35,12 @@ Future workerStartCallback() async { //try to keep the usage of this function BELOW +-30 seconds //to not be punished by the scheduler in future runs. await taskMap[taskName]!.item1(); - } catch (err, stackstrace) { - Logger().e('Error while running $taskName job:', err, stackstrace); + } catch (err, stackTrace) { + Logger().e( + 'Error while running $taskName job:', + error: err, + stackTrace: stackTrace, + ); return false; } return true; diff --git a/uni/lib/controller/fetchers/course_units_fetcher/all_course_units_fetcher.dart b/uni/lib/controller/fetchers/course_units_fetcher/all_course_units_fetcher.dart index f0d87293b..c84932de1 100644 --- a/uni/lib/controller/fetchers/course_units_fetcher/all_course_units_fetcher.dart +++ b/uni/lib/controller/fetchers/course_units_fetcher/all_course_units_fetcher.dart @@ -1,4 +1,3 @@ -import 'package:logger/logger.dart'; import 'package:uni/controller/networking/network_router.dart'; import 'package:uni/controller/parsers/parser_course_units.dart'; import 'package:uni/model/entities/course.dart'; @@ -14,17 +13,12 @@ class AllCourseUnitsFetcher { final allCourseUnits = []; for (final course in courses) { - try { - final courseUnits = await _getAllCourseUnitsAndCourseAveragesFromCourse( - course, - session, - currentCourseUnits: currentCourseUnits, - ); - allCourseUnits.addAll(courseUnits.where((c) => c.enrollmentIsValid())); - } catch (e) { - Logger().e('Failed to fetch course units for ${course.name}', e); - return null; - } + final courseUnits = await _getAllCourseUnitsAndCourseAveragesFromCourse( + course, + session, + currentCourseUnits: currentCourseUnits, + ); + allCourseUnits.addAll(courseUnits.where((c) => c.enrollmentIsValid())); } return allCourseUnits; diff --git a/uni/lib/controller/fetchers/profile_fetcher.dart b/uni/lib/controller/fetchers/profile_fetcher.dart index 56a239d64..17028f34c 100644 --- a/uni/lib/controller/fetchers/profile_fetcher.dart +++ b/uni/lib/controller/fetchers/profile_fetcher.dart @@ -1,4 +1,3 @@ -import 'package:logger/logger.dart'; import 'package:uni/controller/fetchers/courses_fetcher.dart'; import 'package:uni/controller/fetchers/session_dependant_fetcher.dart'; import 'package:uni/controller/networking/network_router.dart'; @@ -30,27 +29,23 @@ class ProfileFetcher implements SessionDependantFetcher { } final profile = Profile.fromResponse(response); - try { - final coursesResponses = await Future.wait( - CoursesFetcher().getCoursesListResponses(session), - ); - final courses = parseMultipleCourses(coursesResponses); - for (final course in courses) { - if (profile.courses - .map((c) => c.festId) - .toList() - .contains(course.festId)) { - profile.courses - .where((c) => c.festId == course.festId) - .first - .state ??= course.state; - continue; - } - profile.courses.add(course); + final coursesResponses = await Future.wait( + CoursesFetcher().getCoursesListResponses(session), + ); + final courses = parseMultipleCourses(coursesResponses); + + for (final course in courses) { + if (profile.courses + .map((c) => c.festId) + .toList() + .contains(course.festId)) { + profile.courses.where((c) => c.festId == course.festId).first.state ??= + course.state; + continue; } - } catch (e) { - Logger().e('Failed to get user courses via scrapping: $e'); + profile.courses.add(course); } + return profile; } } diff --git a/uni/lib/controller/load_static/terms_and_conditions.dart b/uni/lib/controller/load_static/terms_and_conditions.dart index ba6945175..e5e5fc5da 100644 --- a/uni/lib/controller/load_static/terms_and_conditions.dart +++ b/uni/lib/controller/load_static/terms_and_conditions.dart @@ -4,7 +4,6 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:crypto/crypto.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:http/http.dart' as http; -import 'package:logger/logger.dart'; import 'package:uni/controller/local_storage/app_shared_preferences.dart'; /// Returns the content of the Terms and Conditions remote file, @@ -13,25 +12,14 @@ import 'package:uni/controller/local_storage/app_shared_preferences.dart'; /// If this operation is unsuccessful, an error message is returned. Future fetchTermsAndConditions() async { if (await Connectivity().checkConnectivity() != ConnectivityResult.none) { - try { - const url = - 'https://raw.githubusercontent.com/NIAEFEUP/project-schrodinger/develop/uni/assets/text/TermsAndConditions.md'; - final response = await http.get(Uri.parse(url)); - if (response.statusCode == 200) { - return response.body; - } - } catch (e) { - Logger().e('Failed to fetch Terms and Conditions: $e'); + const url = + 'https://raw.githubusercontent.com/NIAEFEUP/project-schrodinger/develop/uni/assets/text/TermsAndConditions.md'; + final response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + return response.body; } } - - try { - return await rootBundle.loadString('assets/text/TermsAndConditions.md'); - } catch (e) { - Logger().e('Failed to read Terms and Conditions: $e'); - return 'Não foi possível carregar os Termos e Condições. ' - 'Por favor tente mais tarde.'; - } + return rootBundle.loadString('assets/text/TermsAndConditions.md'); } /// Checks if the current Terms and Conditions have been accepted by the user, diff --git a/uni/lib/controller/local_storage/app_courses_database.dart b/uni/lib/controller/local_storage/app_courses_database.dart index f9130e050..db7d9ac6a 100644 --- a/uni/lib/controller/local_storage/app_courses_database.dart +++ b/uni/lib/controller/local_storage/app_courses_database.dart @@ -10,7 +10,7 @@ import 'package:uni/model/entities/course.dart'; /// See the [Course] class to see what data is stored in this database. class AppCoursesDatabase extends AppDatabase { AppCoursesDatabase() - : super('courses.db', [createScript], onUpgrade: migrate, version: 2); + : super('courses.db', [createScript], onUpgrade: migrate, version: 3); static const String createScript = '''CREATE TABLE courses(id INTEGER, fest_id INTEGER, name TEXT, ''' '''abbreviation TEXT, currYear TEXT, firstEnrollment INTEGER, state TEXT, ''' diff --git a/uni/lib/controller/local_storage/app_database.dart b/uni/lib/controller/local_storage/app_database.dart index c24800d5a..885cba9f3 100644 --- a/uni/lib/controller/local_storage/app_database.dart +++ b/uni/lib/controller/local_storage/app_database.dart @@ -20,7 +20,10 @@ class AppDatabase { /// A list of commands to be executed on database creation. List commands; - // A lock that synchronizes all database insertions. + /// The lock timeout for database operations. + static const Duration lockTimeout = Duration(seconds: 10); + + /// A lock that synchronizes all database insertions. static Lock lock = Lock(); /// A function that is called when the [version] changes. @@ -42,16 +45,19 @@ class AppDatabase { String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm, }) async { - await lock.synchronized(() async { - final db = await getDatabase(); - - await db.insert( - table, - values, - nullColumnHack: nullColumnHack, - conflictAlgorithm: conflictAlgorithm, - ); - }); + await lock.synchronized( + () async { + final db = await getDatabase(); + + await db.insert( + table, + values, + nullColumnHack: nullColumnHack, + conflictAlgorithm: conflictAlgorithm, + ); + }, + timeout: lockTimeout, + ); } /// Initializes this database. diff --git a/uni/lib/controller/local_storage/app_exams_database.dart b/uni/lib/controller/local_storage/app_exams_database.dart index f56ae0a0a..36264e347 100644 --- a/uni/lib/controller/local_storage/app_exams_database.dart +++ b/uni/lib/controller/local_storage/app_exams_database.dart @@ -10,7 +10,7 @@ import 'package:uni/model/entities/exam.dart'; /// See the [Exam] class to see what data is stored in this database. class AppExamsDatabase extends AppDatabase { AppExamsDatabase() - : super('exams.db', [_createScript], onUpgrade: migrate, version: 4); + : super('exams.db', [_createScript], onUpgrade: migrate, version: 5); Map months = { 'Janeiro': '01', 'Fevereiro': '02', diff --git a/uni/lib/controller/networking/network_router.dart b/uni/lib/controller/networking/network_router.dart index f05d88d54..d84ff6c95 100644 --- a/uni/lib/controller/networking/network_router.dart +++ b/uni/lib/controller/networking/network_router.dart @@ -44,44 +44,47 @@ class NetworkRouter { List faculties, { required bool persistentSession, }) async { - return _loginLock.synchronized(() async { - if (_lastLoginTime != null && - DateTime.now().difference(_lastLoginTime!) < - const Duration(minutes: 1) && - _cachedSession != null) { - Logger().d('Login request ignored due to recent login'); - return _cachedSession; - } + return _loginLock.synchronized( + () async { + if (_lastLoginTime != null && + DateTime.now().difference(_lastLoginTime!) < + const Duration(minutes: 1) && + _cachedSession != null) { + Logger().d('Login request ignored due to recent login'); + return _cachedSession; + } - final url = - '${NetworkRouter.getBaseUrls(faculties)[0]}mob_val_geral.autentica'; - final response = await http.post( - url.toUri(), - body: {'pv_login': username, 'pv_password': password}, - ).timeout(_requestTimeout); + final url = + '${NetworkRouter.getBaseUrls(faculties)[0]}mob_val_geral.autentica'; + final response = await http.post( + url.toUri(), + body: {'pv_login': username, 'pv_password': password}, + ).timeout(_requestTimeout); - if (response.statusCode != 200) { - Logger().e('Login failed with status code ${response.statusCode}'); - return null; - } + if (response.statusCode != 200) { + Logger().e('Login failed with status code ${response.statusCode}'); + return null; + } - final session = Session.fromLogin( - response, - faculties, - persistentSession: persistentSession, - ); + final session = Session.fromLogin( + response, + faculties, + persistentSession: persistentSession, + ); - if (session == null) { - Logger().e('Login failed: user not authenticated'); - return null; - } + if (session == null) { + Logger().e('Login failed: user not authenticated'); + return null; + } - Logger().i('Login successful'); - _lastLoginTime = DateTime.now(); - _cachedSession = session; + Logger().i('Login successful'); + _lastLoginTime = DateTime.now(); + _cachedSession = session; - return session; - }); + return session; + }, + timeout: _requestTimeout, + ); } /// Re-authenticates the user via the Sigarra API @@ -182,8 +185,10 @@ class NetworkRouter { final newSession = await reLoginFromSession(session); if (newSession == null) { - NavigationService.logoutAndPopHistory(null); - return Future.error('Login failed'); + NavigationService.logoutAndPopHistory(); + return Future.error( + 'Re-login failed; user might have changed password', + ); } session diff --git a/uni/lib/controller/parsers/parser_course_units.dart b/uni/lib/controller/parsers/parser_course_units.dart index c57764d2a..f9a1d7949 100644 --- a/uni/lib/controller/parsers/parser_course_units.dart +++ b/uni/lib/controller/parsers/parser_course_units.dart @@ -82,7 +82,7 @@ List parseCourseUnitsAndCourseAverage( codeName, // FIXME: this is not the abbreviation status: status, grade: grade, - ects: double.parse(ects), + ects: double.tryParse(ects), name: name, curricularYear: int.parse(year), semesterCode: semester, diff --git a/uni/lib/main.dart b/uni/lib/main.dart index 407d18030..01fac7edd 100644 --- a/uni/lib/main.dart +++ b/uni/lib/main.dart @@ -89,7 +89,12 @@ Future main() async { await dotenv .load(fileName: 'assets/env/.env', isOptional: true) .onError((error, stackTrace) { - Logger().e('Error loading .env file: $error', error, stackTrace); + Sentry.captureException(error, stackTrace: stackTrace); + Logger().e( + 'Error loading .env file: $error', + error: error, + stackTrace: stackTrace, + ); }); final savedTheme = await AppSharedPreferences.getThemeMode(); @@ -148,28 +153,29 @@ Future main() async { create: (_) => ThemeNotifier(savedTheme), ), ], - child: MyApp(route), + child: Application(route), ), ); }, ); } -/// Manages the state of the app -/// +/// Manages the state of the app. /// This class is necessary to track the app's state for -/// the current execution -class MyApp extends StatefulWidget { - const MyApp(this.initialRoute, {super.key}); +/// the current execution. +class Application extends StatefulWidget { + const Application(this.initialRoute, {super.key}); final String initialRoute; + static GlobalKey navigatorKey = GlobalKey(); + @override - State createState() => MyAppState(); + State createState() => ApplicationState(); } /// Manages the app depending on its current state -class MyAppState extends State { +class ApplicationState extends State { @override Widget build(BuildContext context) { SystemChrome.setPreferredOrientations([ @@ -178,6 +184,7 @@ class MyAppState extends State { return Consumer2( builder: (context, themeNotifier, localeNotifier, _) => MaterialApp( title: 'uni', + navigatorKey: Application.navigatorKey, theme: applicationLightTheme, darkTheme: applicationDarkTheme, themeMode: themeNotifier.getTheme(), diff --git a/uni/lib/model/entities/course.dart b/uni/lib/model/entities/course.dart index 1c739fb42..645aa7cef 100644 --- a/uni/lib/model/entities/course.dart +++ b/uni/lib/model/entities/course.dart @@ -23,8 +23,8 @@ class Course { /// Creates a new instance from a JSON object. Course.fromJson(Map data) - : id = data['cur_id'] as int, - festId = data['fest_id'] as int, + : id = (data['cur_id'] ?? 0) as int, + festId = (data['fest_id'] ?? 0) as int, name = data['cur_nome'] as String?, currYear = data['ano_curricular'] as String?, firstEnrollment = data['fest_a_lect_1_insc'] as int, diff --git a/uni/lib/model/entities/exam.dart b/uni/lib/model/entities/exam.dart index fb29f704a..1bfe20d0f 100644 --- a/uni/lib/model/entities/exam.dart +++ b/uni/lib/model/entities/exam.dart @@ -1,5 +1,4 @@ import 'package:intl/intl.dart'; -import 'package:logger/logger.dart'; import 'package:uni/model/entities/app_locale.dart'; /// Manages a generic Exam. @@ -87,11 +86,6 @@ class Exam { return '''$id - $subject - ${begin.year} - $month - ${begin.day} - $beginTime-$endTime - $type - $rooms - $weekDay'''; } - /// Prints the data in this exam to the [Logger] with an INFO level. - void printExam() { - Logger().i(toString()); - } - @override bool operator ==(Object other) => identical(this, other) || other is Exam && id == other.id; diff --git a/uni/lib/model/entities/trip.dart b/uni/lib/model/entities/trip.dart index 73b4ce6b4..616ce1c62 100644 --- a/uni/lib/model/entities/trip.dart +++ b/uni/lib/model/entities/trip.dart @@ -1,5 +1,3 @@ -import 'package:logger/logger.dart'; - /// Stores information about a bus trip. class Trip { Trip({ @@ -20,11 +18,6 @@ class Trip { }; } - /// Prints the data in this trip to the [Logger] with an INFO level. - void printTrip() { - Logger().i('$line ($destination) - $timeRemaining'); - } - /// Compares the remaining time of two trips. int compare(Trip other) { return timeRemaining.compareTo(other.timeRemaining); diff --git a/uni/lib/model/providers/state_provider_notifier.dart b/uni/lib/model/providers/state_provider_notifier.dart index 81543fd8b..6f4bbd9a7 100644 --- a/uni/lib/model/providers/state_provider_notifier.dart +++ b/uni/lib/model/providers/state_provider_notifier.dart @@ -22,6 +22,7 @@ abstract class StateProviderNotifier extends ChangeNotifier { _initializedFromStorage = !initialize, _initializedFromRemote = !initialize; + static const lockTimeout = Duration(seconds: 10); final Lock _lock = Lock(); final RequestStatus _initialStatus; RequestStatus _status; @@ -124,12 +125,18 @@ abstract class StateProviderNotifier extends ChangeNotifier { } Future forceRefresh(BuildContext context) async { - await _lock.synchronized(() async { - final session = context.read().session; - final profile = context.read().profile; - _updateStatus(RequestStatus.busy); - await _loadFromRemote(session, profile, force: true); - }); + await _lock.synchronized( + () async { + if (!context.mounted) { + return; + } + final session = context.read().session; + final profile = context.read().profile; + _updateStatus(RequestStatus.busy); + await _loadFromRemote(session, profile, force: true); + }, + timeout: lockTimeout, + ); } Future ensureInitialized(BuildContext context) async { @@ -141,32 +148,38 @@ abstract class StateProviderNotifier extends ChangeNotifier { } Future ensureInitializedFromRemote(BuildContext context) async { - await _lock.synchronized(() async { - if (_initializedFromRemote) { - return; - } + await _lock.synchronized( + () async { + if (_initializedFromRemote || !context.mounted) { + return; + } - _initializedFromRemote = true; + final session = context.read().session; + final profile = context.read().profile; - final session = context.read().session; - final profile = context.read().profile; + _initializedFromRemote = true; - await _loadFromRemote(session, profile); - }); + await _loadFromRemote(session, profile); + }, + timeout: lockTimeout, + ); } /// Loads data from storage into the provider. /// This will run once when the provider is first initialized. /// If the data is not available in storage, this method should do nothing. Future ensureInitializedFromStorage() async { - await _lock.synchronized(() async { - if (_initializedFromStorage) { - return; - } - - _initializedFromStorage = true; - await _loadFromStorage(); - }); + await _lock.synchronized( + () async { + if (_initializedFromStorage) { + return; + } + + _initializedFromStorage = true; + await _loadFromStorage(); + }, + timeout: lockTimeout, + ); } Future loadFromStorage(); diff --git a/uni/lib/view/bug_report/widgets/form.dart b/uni/lib/view/bug_report/widgets/form.dart index c94af32b6..1b8321ad8 100644 --- a/uni/lib/view/bug_report/widgets/form.dart +++ b/uni/lib/view/bug_report/widgets/form.dart @@ -246,7 +246,7 @@ class BugReportFormState extends State { child: Text( S.of(context).send, style: const TextStyle( - /*color: Colors.white*/ fontSize: 20, + fontSize: 20, ), ), ); @@ -276,7 +276,8 @@ class BugReportFormState extends State { Logger().i('Successfully submitted bug report.'); if (context.mounted) toastMsg = S.of(context).success; status = true; - } catch (e) { + } catch (e, stackTrace) { + await Sentry.captureException(e, stackTrace: stackTrace); Logger().e('Error while posting bug report:$e'); if (context.mounted) toastMsg = S.of(context).sent_error; status = false; @@ -284,14 +285,17 @@ class BugReportFormState extends State { clearForm(); - if (mounted) { + if (context.mounted) { FocusScope.of(context).requestFocus(FocusNode()); status ? await ToastMessage.success(context, toastMsg) : await ToastMessage.error(context, toastMsg); - setState(() { - _isButtonTapped = false; - }); + + if (context.mounted) { + setState(() { + _isButtonTapped = false; + }); + } } } diff --git a/uni/lib/view/common_widgets/page_transition.dart b/uni/lib/view/common_widgets/page_transition.dart index 2f3581a0d..da1273089 100644 --- a/uni/lib/view/common_widgets/page_transition.dart +++ b/uni/lib/view/common_widgets/page_transition.dart @@ -56,7 +56,7 @@ class PageTransition { case TermsAndConditionsState.accepted: return; case TermsAndConditionsState.rejected: - NavigationService.logoutAndPopHistory(null); + NavigationService.logoutAndPopHistory(); } } } diff --git a/uni/lib/view/locations/widgets/faculty_map.dart b/uni/lib/view/locations/widgets/faculty_map.dart index 6b3203302..1e2354e54 100644 --- a/uni/lib/view/locations/widgets/faculty_map.dart +++ b/uni/lib/view/locations/widgets/faculty_map.dart @@ -13,9 +13,9 @@ class FacultyMap extends StatelessWidget { switch (faculty) { case 'FEUP': return LocationsMap( - northEastBoundary: LatLng(41.17986, -8.59298), - southWestBoundary: LatLng(41.17670, -8.59991), - center: LatLng(41.17731, -8.59522), + northEastBoundary: const LatLng(41.17986, -8.59298), + southWestBoundary: const LatLng(41.17670, -8.59991), + center: const LatLng(41.17731, -8.59522), locations: locations, ); default: diff --git a/uni/lib/view/login/login.dart b/uni/lib/view/login/login.dart index a390331d3..476fa5af8 100644 --- a/uni/lib/view/login/login.dart +++ b/uni/lib/view/login/login.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:logger/logger.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/login_exceptions.dart'; import 'package:uni/model/providers/startup/session_provider.dart'; @@ -68,7 +69,7 @@ class LoginPageViewState extends State { _loggingIn = false; }); } - } catch (error) { + } catch (error, stackTrace) { setState(() { _loggingIn = false; }); @@ -79,7 +80,8 @@ class LoginPageViewState extends State { } else if (error is WrongCredentialsException) { unawaited(ToastMessage.error(context, error.message)); } else { - Logger().e(error); + Logger().e(error, stackTrace: stackTrace); + unawaited(Sentry.captureException(error, stackTrace: stackTrace)); unawaited(ToastMessage.error(context, S.of(context).failed_login)); } } diff --git a/uni/lib/view/navigation_service.dart b/uni/lib/view/navigation_service.dart index 9e8c336b6..903f5410e 100644 --- a/uni/lib/view/navigation_service.dart +++ b/uni/lib/view/navigation_service.dart @@ -1,21 +1,22 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:uni/controller/cleanup.dart'; +import 'package:uni/main.dart'; import 'package:uni/utils/drawer_items.dart'; import 'package:uni/view/login/login.dart'; /// Manages the navigation logic class NavigationService { - static final GlobalKey navigatorKey = - GlobalKey(); + static void logoutAndPopHistory() { + final context = Application.navigatorKey.currentContext!; - static void logoutAndPopHistory(BuildContext? dataContext) { - if (dataContext != null) { - cleanupStoredData(dataContext); - } + unawaited(cleanupStoredData(context)); - navigatorKey.currentState?.pushNamedAndRemoveUntil( - '/${DrawerItem.navLogOut.title}', - (_) => false, + Navigator.pushNamedAndRemoveUntil( + context, + '/${DrawerItem.navLogIn.title}', + (route) => false, ); } diff --git a/uni/pubspec.yaml b/uni/pubspec.yaml index 3491c6fe4..4813dba19 100644 --- a/uni/pubspec.yaml +++ b/uni/pubspec.yaml @@ -30,17 +30,17 @@ dependencies: flutter_local_notifications: ^15.1.0+1 flutter_localizations: sdk: flutter - flutter_map: ^4.0.0 + flutter_map: ^5.0.0 flutter_map_marker_popup: ^5.0.0 flutter_markdown: ^0.6.0 flutter_svg: ^2.0.0+1 flutter_widget_from_html_core: ^0.10.3 html: ^0.15.0 - http: ^0.13.0 + http: ^1.1.0 image: ^4.0.13 intl: ^0.18.0 - latlong2: ^0.8.1 - logger: ^1.1.0 + latlong2: ^0.9.0 + logger: ^2.0.2+1 material_design_icons_flutter: ^7.0.7296 path: ^1.8.0 path_provider: ^2.0.0