From 3e651f02338adff3f836df74f05bccddfba45782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20K=C3=B6rber?= <56073945+jakobkoerber@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:12:58 +0100 Subject: [PATCH] User Interface Enhancements (#221) --- .github/workflows/lint_test_build.yml | 4 +- README.md | 2 +- .../widgets/calendar/CalendarWidget.kt | 38 +- .../example_appwidget_preview.png | Bin 3522 -> 0 bytes .../main/res/drawable/appwidget_preview.png | Bin 0 -> 178936 bytes .../src/main/res/layout/calendar_widget.xml | 11 + .../app/src/main/res/values-de/strings.xml | 1 + android/app/src/main/res/values/strings.xml | 1 + .../src/main/res/xml/calendar_widget_info.xml | 4 +- .../CalendarWidgetContent.swift | 39 +- ios/Podfile.lock | 6 + ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- ios/Runner/Localizable.xcstrings | 30 + lib/base/enums/user_preference.dart | 1 - .../errorHandling/error_handling_router.dart | 13 +- lib/base/errorHandling/grpc_error_router.dart | 34 + lib/base/helpers/directions_launcher.dart | 43 - lib/base/helpers/url_launcher.dart | 45 - lib/base/localization/app_de.arb | 17 +- lib/base/localization/app_en.arb | 17 +- .../apis/tumdev/cache_interceptor.dart | 113 --- .../networking/apis/tumdev/cached_client.dart | 83 -- .../apis/tumdev/cached_response.dart | 37 - .../apis/tumdev/custom_hive_cache_store.dart | 48 -- .../apis/tumdev/lru_memory_cache_store.dart | 34 - lib/base/networking/base/api_response.dart | 23 +- .../base/custom_cache_interceptor.dart | 77 -- lib/base/networking/base/grpc_client.dart | 62 ++ lib/base/networking/base/rest_client.dart | 88 +- lib/base/networking/cache/cache.dart | 80 ++ lib/base/networking/cache/cache_entry.dart | 20 + lib/base/networking/cache/cache_entry.g.dart | 805 ++++++++++++++++++ .../cache/grpc_cache_interceptor.dart | 76 ++ .../cache/rest_cache_interceptor.dart | 81 ++ lib/base/routing/router.dart | 2 +- lib/base/services/location_service.dart | 7 +- .../{helpers => util}/card_with_padding.dart | 0 .../{helpers => util}/custom_back_button.dart | 0 .../delayed_loading_indicator.dart | 0 lib/base/util/directions_launcher.dart | 72 ++ lib/base/{helpers => util}/enum_parser.dart | 0 lib/base/util/fast_hash.dart | 15 + .../fullscreen_image_view.dart | 11 +- .../{helpers => util}/horizontal_slider.dart | 0 .../{helpers => util}/hyperlink_text.dart | 2 +- lib/base/{helpers => util}/icon_text.dart | 0 lib/base/{helpers => util}/info_row.dart | 2 + .../{helpers => util}/last_updated_text.dart | 0 .../{helpers => util}/padded_divider.dart | 0 .../{helpers => util}/placeholder_text.dart | 2 +- .../{helpers => util}/read_list_value.dart | 0 .../semester_calculator.dart | 0 lib/base/{helpers => util}/shimmer_view.dart | 0 lib/base/{helpers => util}/string_parser.dart | 15 +- lib/base/util/url_launcher.dart | 36 + lib/base/views/seperated_list.dart | 2 +- .../services/calendar_service.dart | 6 +- .../views/calendars_view.dart | 4 +- .../views/event_creation_view.dart | 2 +- .../homeWidget/calendar_widget_view.dart | 4 +- lib/departuresComponent/model/departure.dart | 2 +- .../model/mvv_response.dart | 2 +- .../services/departures_service.dart | 2 +- .../viewModel/departures_viewmodel.dart | 2 +- .../views/departures_details_row_view.dart | 2 +- .../views/departures_details_view.dart | 18 +- .../homeWidget/departures_widget_view.dart | 4 +- .../services/feedback_service.dart | 4 +- .../views/feedback_form_view.dart | 4 +- .../views/feedback_textfield.dart | 2 +- lib/gradeComponent/model/average_grade.dart | 2 +- .../services/grade_service.dart | 4 +- .../viewModels/grade_viewmodel.dart | 4 +- lib/gradeComponent/views/chart_view.dart | 6 +- lib/gradeComponent/views/grade_rectangle.dart | 2 +- lib/gradeComponent/views/grade_view.dart | 2 +- lib/gradeComponent/views/grades_view.dart | 10 +- .../views/contact_card_loading_view.dart | 4 +- .../views/contact_card_view.dart | 12 +- .../contactComponent/views/contact_view.dart | 2 +- .../contactComponent/views/link_view.dart | 17 +- .../contactComponent/views/tuition_view.dart | 6 +- lib/homeComponent/home_screen.dart | 2 +- .../views/preference_selection_view.dart | 2 +- .../widgetComponent/views/widget_screen.dart | 3 +- .../model/lecture_details.dart | 2 +- .../services/lecture_details_service.dart | 2 +- .../services/lecture_search_service.dart | 2 +- .../services/lecture_service.dart | 2 +- .../views/detailed_lecture_info_row_view.dart | 2 +- .../views/lecture_details_view.dart | 6 +- .../views/lecture_info_card_view.dart | 2 +- .../views/lecture_links_view.dart | 2 +- lib/lectureComponent/views/lecture_view.dart | 2 +- lib/lectureComponent/views/lectures_view.dart | 10 +- lib/main.dart | 64 +- lib/movieComponent/service/movie_service.dart | 4 +- .../views/homeWidget/movie_card_view.dart | 4 +- .../views/homeWidget/movies_widget_view.dart | 4 +- .../services/navigatum_search_service.dart | 2 +- .../services/navigatum_service.dart | 4 +- .../views/navigatum_room_maps_view.dart | 4 +- .../views/navigatum_room_view.dart | 4 +- lib/navigation_service.dart | 19 +- lib/newsComponent/service/news_service.dart | 6 +- .../views/homeWidget/news_widget_view.dart | 4 +- lib/newsComponent/views/news_card_view.dart | 2 +- .../services/onboarding_service.dart | 9 +- .../viewModels/onboarding_viewmodel.dart | 2 +- .../views/confirm_view.dart | 6 +- lib/onboardingComponent/views/login_view.dart | 3 +- .../views/permission_check_view.dart | 2 +- .../services/person_details_service.dart | 2 +- .../views/person_details_view.dart | 6 +- .../services/person_search_service.dart | 2 +- .../model/cafeterias/cafeteria.dart | 2 +- .../services/cafeterias_service.dart | 2 +- .../services/mealplan_service.dart | 2 +- .../services/study_rooms_service.dart | 2 +- .../viewModels/study_rooms_viewmodel.dart | 38 +- .../views/cafeterias/cafeteria_row_view.dart | 2 +- .../views/cafeterias/cafeteria_view.dart | 17 +- .../views/cafeterias/cafeterias_view.dart | 6 +- .../views/cafeterias/dish_card_view.dart | 2 +- .../views/cafeterias/dish_slider_view.dart | 2 +- .../views/campuses/campus_map_view.dart | 2 +- .../campuses/campus_most_searched_view.dart | 4 +- .../views/campuses/campus_scaffold.dart | 2 +- .../views/campuses/campus_view.dart | 2 +- .../views/directions_button.dart | 10 +- .../homeWidget/cafeteria_widget_view.dart | 2 +- .../homeWidget/study_room_widget_view.dart | 8 +- lib/placesComponent/views/places_screen.dart | 2 +- lib/placesComponent/views/places_view.dart | 2 +- .../study_room_group_scaffold.dart | 2 +- .../studyGroups/study_room_group_view.dart | 4 +- .../views/studyGroups/study_rooms_view.dart | 6 +- lib/profileComponent/model/tuition.dart | 2 +- .../services/profile_service.dart | 4 +- .../viewModels/global_search_viewmodel.dart | 4 + .../calendar_search_result_view.dart | 4 +- .../resultViews/movie_search_result_view.dart | 4 +- .../resultViews/news_search_result_view.dart | 2 +- .../search_category_picker_view.dart | 32 +- .../search_result_card_view.dart | 4 +- .../search_result_details_view.dart | 4 +- .../views/appWideSearch/search_scaffold.dart | 2 +- .../views/personRoomSearch/search_view.dart | 4 +- .../service/user_preferences_service.dart | 1 - .../user_preferences_viewmodel.dart | 44 +- .../views/appearance_settings_view.dart | 7 +- lib/settingsComponent/views/contact_view.dart | 14 +- .../views/default_maps_picker_view.dart | 51 -- .../views/settings_scaffold.dart | 2 +- .../services/student_card_service.dart | 2 +- .../views/information_view.dart | 4 +- .../views/snapping_slider.dart | 2 +- .../views/student_card_view.dart | 6 +- pubspec.lock | 90 +- pubspec.yaml | 94 +- web/favicon.png | Bin 1941 -> 0 bytes web/icons/Icon-192.png | Bin 4359 -> 0 bytes web/icons/Icon-512.png | Bin 13644 -> 0 bytes web/index.html | 141 --- web/manifest.json | 23 - web/splash/img/dark-1x.png | Bin 1440 -> 0 bytes web/splash/img/dark-2x.png | Bin 3646 -> 0 bytes web/splash/img/dark-3x.png | Bin 6937 -> 0 bytes web/splash/img/dark-4x.png | Bin 11472 -> 0 bytes web/splash/img/light-1x.png | Bin 1440 -> 0 bytes web/splash/img/light-2x.png | Bin 3646 -> 0 bytes web/splash/img/light-3x.png | Bin 6937 -> 0 bytes web/splash/img/light-4x.png | Bin 11472 -> 0 bytes 174 files changed, 1847 insertions(+), 1245 deletions(-) delete mode 100644 android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png create mode 100644 android/app/src/main/res/drawable/appwidget_preview.png create mode 100644 lib/base/errorHandling/grpc_error_router.dart delete mode 100644 lib/base/helpers/directions_launcher.dart delete mode 100644 lib/base/helpers/url_launcher.dart delete mode 100644 lib/base/networking/apis/tumdev/cache_interceptor.dart delete mode 100644 lib/base/networking/apis/tumdev/cached_client.dart delete mode 100644 lib/base/networking/apis/tumdev/cached_response.dart delete mode 100644 lib/base/networking/apis/tumdev/custom_hive_cache_store.dart delete mode 100644 lib/base/networking/apis/tumdev/lru_memory_cache_store.dart delete mode 100644 lib/base/networking/base/custom_cache_interceptor.dart create mode 100644 lib/base/networking/base/grpc_client.dart create mode 100644 lib/base/networking/cache/cache.dart create mode 100644 lib/base/networking/cache/cache_entry.dart create mode 100644 lib/base/networking/cache/cache_entry.g.dart create mode 100644 lib/base/networking/cache/grpc_cache_interceptor.dart create mode 100644 lib/base/networking/cache/rest_cache_interceptor.dart rename lib/base/{helpers => util}/card_with_padding.dart (100%) rename lib/base/{helpers => util}/custom_back_button.dart (100%) rename lib/base/{helpers => util}/delayed_loading_indicator.dart (100%) create mode 100644 lib/base/util/directions_launcher.dart rename lib/base/{helpers => util}/enum_parser.dart (100%) create mode 100644 lib/base/util/fast_hash.dart rename lib/base/{helpers => util}/fullscreen_image_view.dart (96%) rename lib/base/{helpers => util}/horizontal_slider.dart (100%) rename lib/base/{helpers => util}/hyperlink_text.dart (95%) rename lib/base/{helpers => util}/icon_text.dart (100%) rename lib/base/{helpers => util}/info_row.dart (76%) rename lib/base/{helpers => util}/last_updated_text.dart (100%) rename lib/base/{helpers => util}/padded_divider.dart (100%) rename lib/base/{helpers => util}/placeholder_text.dart (91%) rename lib/base/{helpers => util}/read_list_value.dart (100%) rename lib/base/{helpers => util}/semester_calculator.dart (100%) rename lib/base/{helpers => util}/shimmer_view.dart (100%) rename lib/base/{helpers => util}/string_parser.dart (90%) create mode 100644 lib/base/util/url_launcher.dart delete mode 100644 lib/settingsComponent/views/default_maps_picker_view.dart delete mode 100644 web/favicon.png delete mode 100644 web/icons/Icon-192.png delete mode 100644 web/icons/Icon-512.png delete mode 100644 web/index.html delete mode 100644 web/manifest.json delete mode 100644 web/splash/img/dark-1x.png delete mode 100644 web/splash/img/dark-2x.png delete mode 100644 web/splash/img/dark-3x.png delete mode 100644 web/splash/img/dark-4x.png delete mode 100644 web/splash/img/light-1x.png delete mode 100644 web/splash/img/light-2x.png delete mode 100644 web/splash/img/light-3x.png delete mode 100644 web/splash/img/light-4x.png diff --git a/.github/workflows/lint_test_build.yml b/.github/workflows/lint_test_build.yml index 90e25fcd..c8a8d178 100644 --- a/.github/workflows/lint_test_build.yml +++ b/.github/workflows/lint_test_build.yml @@ -48,5 +48,5 @@ jobs: - name: Build Android run: flutter build apk - - name: Build Website - run: flutter build web --base-href / + #- name: Build Website + # run: flutter build web --base-href / diff --git a/README.md b/README.md index b8361eb4..35ec120e 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Check out our detailed information at [CONTRIBUTING.md](https://github.com/TCA-T --> ## Beta -If you want to participate in the beta of this app, enter your details [here](https://testflight.apple.com/join/4Ddi6f2f) to get invited via TestFlight or [here](https://play.google.com/store/apps/details?id=de.tum.tca_flutter) to get invited via the Google Play Beta Program. You can also test the preliminary [web app](https://web.tum.app/). We would appreciate your feedback regarding bugs and improvement suggestions! +If you want to participate in the beta of this app, enter your details [here](https://testflight.apple.com/join/4Ddi6f2f) to get invited via TestFlight or [here](https://play.google.com/store/apps/details?id=de.tum.tca_flutter) to get invited via the Google Play Beta Program. We would appreciate your feedback regarding bugs and improvement suggestions! ## Policies - [Privacy policy](https://app.tum.de/landing/privacy/) diff --git a/android/app/src/main/kotlin/de/tum/in/tumcampus/widgets/calendar/CalendarWidget.kt b/android/app/src/main/kotlin/de/tum/in/tumcampus/widgets/calendar/CalendarWidget.kt index 31dea124..b98bfe32 100644 --- a/android/app/src/main/kotlin/de/tum/in/tumcampus/widgets/calendar/CalendarWidget.kt +++ b/android/app/src/main/kotlin/de/tum/in/tumcampus/widgets/calendar/CalendarWidget.kt @@ -5,6 +5,7 @@ import android.appwidget.AppWidgetProvider import android.content.Context import android.content.Intent import android.net.Uri +import android.view.View import android.widget.RemoteViews import de.tum.`in`.tumcampus.MainActivity import de.tum.`in`.tumcampus.R @@ -12,6 +13,8 @@ import de.tum.`in`.tumcampus.util.deserializeStringToDate import es.antonborri.home_widget.HomeWidgetLaunchIntent import es.antonborri.home_widget.HomeWidgetPlugin import org.joda.time.DateTime +import org.joda.time.Days +import org.joda.time.LocalDate import org.joda.time.format.DateTimeFormat import org.ocpsoft.prettytime.PrettyTime import java.util.Locale @@ -48,32 +51,31 @@ private fun updateAppWidget( val p = PrettyTime() val widgetData = HomeWidgetPlugin.getData(context) val lastSaved = widgetData.getString("calendar_save", null) - val lastSavedDate = p.format(deserializeStringToDate(lastSaved).toDate()) - remoteViews.setTextViewText(R.id.calendar_widget_updated_on, lastSavedDate) + val lastSavedDate = deserializeStringToDate(lastSaved).toLocalDate() + val lastSavedDateString = p.format( deserializeStringToDate(lastSaved).toDate()) + remoteViews.setTextViewText(R.id.calendar_widget_updated_on, lastSavedDateString) - // dee val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity( context, MainActivity::class.java, Uri.parse("tumCampusApp://message?homeWidget=calendar")) remoteViews.setOnClickPendingIntent(R.id.calendar_widget, pendingIntentWithData) - // Set up the calendar activity listeners - val pendingCalendarIntent = HomeWidgetLaunchIntent.getActivity(context, MainActivity::class.java, Uri.parse("/calendar")) - remoteViews.setOnClickPendingIntent(R.id.calendar_widget_header, pendingCalendarIntent) - remoteViews.setPendingIntentTemplate(R.id.calendar_widget_listview, pendingCalendarIntent) + if (Days.daysBetween(lastSavedDate, LocalDate.now()).days < 14) { + // Set up the intent that starts the calendarWidgetService + val intent = Intent(context, CalendarWidgetService::class.java) + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)) + remoteViews.setRemoteAdapter(R.id.calendar_widget_listview, intent) - // Set up the intent that starts the calendarWidgetService, which will - // provide the departure times for this station - val intent = Intent(context, CalendarWidgetService::class.java) - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)) - remoteViews.setRemoteAdapter(R.id.calendar_widget_listview, intent) - - // The empty view is displayed when the collection has no items. - // It should be in the same layout used to instantiate the RemoteViews - // object above. - remoteViews.setEmptyView(R.id.calendar_widget_listview, R.id.empty_list_item) + // The empty view is displayed when the collection has no items. + // It should be in the same layout used to instantiate the RemoteViews + // object above. + remoteViews.setEmptyView(R.id.calendar_widget_listview, R.id.empty_list_item) + } else { + remoteViews.setViewVisibility(R.id.calendar_widget_listview, View.INVISIBLE) + remoteViews.setViewVisibility(R.id.old_data_item, View.VISIBLE) + } // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, remoteViews) diff --git a/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png b/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png deleted file mode 100644 index 894b069a4907d258f60b1b2406b90f5a0fe1c35b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3522 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

99N%rS~d>wBotm0k)2U9jLoxC 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

S*Ck44MyuVCx7=ZwO4+A+48qD#N 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 QAO!rAmr;@~lQanUf73}By#N3J literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/layout/calendar_widget.xml b/android/app/src/main/res/layout/calendar_widget.xml index 828c711c..b5b6aee0 100644 --- a/android/app/src/main/res/layout/calendar_widget.xml +++ b/android/app/src/main/res/layout/calendar_widget.xml @@ -73,6 +73,17 @@ android:visibility="gone" tools:visibility="visible" /> + + \ No newline at end of file diff --git a/android/app/src/main/res/values-de/strings.xml b/android/app/src/main/res/values-de/strings.xml index 19aec118..eb820c3e 100644 --- a/android/app/src/main/res/values-de/strings.xml +++ b/android/app/src/main/res/values-de/strings.xml @@ -6,6 +6,7 @@ Calendar Widget TUM Vorlesungen auswählen Keine Vorlesungen geplannt :) + Dieses Widget wurde zuletzt vor über 2 Wochen aktualisiert. Öffne die Applikation, um die Daten zu aktualisieren in Minuten Jetzt diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 69c2a94c..d108e6ab 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ calendar Widget TUM Select lectures No lectures planned :) + This widget was last updated over 2 weeks ago.Open the application to update the data in minutes Just now diff --git a/android/app/src/main/res/xml/calendar_widget_info.xml b/android/app/src/main/res/xml/calendar_widget_info.xml index 64caac91..fe500127 100644 --- a/android/app/src/main/res/xml/calendar_widget_info.xml +++ b/android/app/src/main/res/xml/calendar_widget_info.xml @@ -7,10 +7,10 @@ android:minResizeHeight="110dp" android:minWidth="180dp" android:minResizeWidth="180dp" - android:previewImage="@drawable/example_appwidget_preview" + android:previewImage="@drawable/appwidget_preview" android:previewLayout="@layout/calendar_widget" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="86400000" android:widgetCategory="home_screen" /> \ No newline at end of file + android:targetCellHeight="1" --> diff --git a/ios/CalendarWidget/CalendarWidgetContent.swift b/ios/CalendarWidget/CalendarWidgetContent.swift index 8bd4d8ca..db1be0d4 100644 --- a/ios/CalendarWidget/CalendarWidgetContent.swift +++ b/ios/CalendarWidget/CalendarWidgetContent.swift @@ -6,26 +6,29 @@ // import SwiftUI +import WidgetKit struct CalendarWidgetContent: View { @Environment(\.colorScheme) var colorScheme - + var availableItems: Int + var widgetFamily: WidgetFamily var events: [Date: [CalendarEntry]] = [:] var updatedAt: Date - + init(entry: CalendarWidgetEntry) { self.availableItems = entry.size == .systemLarge ? 5 : 2 + self.widgetFamily = entry.size self.updatedAt = entry.date self.events = groupEntriesByDate(entries: Array(entry.entries.prefix(availableItems))) } - + private var dateFormatter: DateFormatter { let formatter = DateFormatter() formatter.setLocalizedDateFormatFromTemplate("MMMM d, yyyy") return formatter } - + var body: some View { VStack { VStack(alignment: .leading) { @@ -40,20 +43,34 @@ struct CalendarWidgetContent: View { .background(.accent) .foregroundStyle(.white) .padding(EdgeInsets(top: 0, leading: 0, bottom: 2, trailing: 0)) - - ForEach(events.keys.sorted(), id: \.self) { eventDate in - ForEach((events[eventDate] ?? []).indices, id: \.self) { eventIndex in - let event = events[eventDate]![eventIndex] - CalendarEventView(event: event, color: event.eventColor, isFirst: eventIndex == 0) + + if (events.isEmpty) { + Spacer() + Text("No lectures planned :)") + .font(.footnote) + .padding() + } else if (Date().timeIntervalSince(updatedAt) > (14 * 24 * 60 * 60)) { + // 14 days * 24 hours * 60 minutes * 60 seconds + Spacer() + Text(widgetFamily != .systemSmall ? "This widget was last updated over 2 weeks ago.\nOpen the application to update the data" : "Open the application to update the widget") + .font(.footnote) + .multilineTextAlignment(.center) + .padding() + } else { + ForEach(events.keys.sorted(), id: \.self) { eventDate in + ForEach((events[eventDate] ?? []).indices, id: \.self) { eventIndex in + let event = events[eventDate]![eventIndex] + CalendarEventView(event: event, color: event.eventColor, isFirst: eventIndex == 0) + } } } Spacer() } } - + func groupEntriesByDate(entries: [CalendarEntry]) -> [Date: [CalendarEntry]] { var groupedEntries: [Date: [CalendarEntry]] = [:] - + for entry in entries { let calendar = Calendar.current let startDateComponents = calendar.dateComponents([.year, .month, .day], from: entry.startDate) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b79c12a2..ad77ac12 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -75,6 +75,8 @@ PODS: - GoogleUtilities/Privacy - home_widget (0.0.1): - Flutter + - isar_flutter_libs (1.0.0): + - Flutter - map_launcher (0.0.1): - Flutter - nanopb (2.30910.0): @@ -116,6 +118,7 @@ DEPENDENCIES: - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`) + - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) - map_launcher (from `.symlinks/plugins/map_launcher/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -161,6 +164,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/google_maps_flutter_ios/ios" home_widget: :path: ".symlinks/plugins/home_widget/ios" + isar_flutter_libs: + :path: ".symlinks/plugins/isar_flutter_libs/ios" map_launcher: :path: ".symlinks/plugins/map_launcher/ios" package_info_plus: @@ -200,6 +205,7 @@ SPEC CHECKSUMS: GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 + isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 map_launcher: e325db1261d029ff33e08e03baccffe09593ffea nanopb: 438bc412db1928dac798aa6fd75726007be04262 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e4ec0e2d..fbdfaf50 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -271,7 +271,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1520; - LastUpgradeCheck = 1530; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 28C6B8952B6AEB8500DD5E9A = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fcac5347..5e31d3d3 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Function(bool)? retry, + Future Function(bool, BuildContext)? retryWithContext, + Color? titleColor, + Color? bodyColor, + }) { + this.errorHandlingViewType = errorHandlingViewType; + this.retry = retry; + this.retryWithContext = retryWithContext; + this.titleColor = titleColor; + this.bodyColor = bodyColor; + } + + final GrpcError grpcError; + + @override + Widget build(BuildContext context) { + return exceptionMessage( + context, + grpcError.message ?? context.localizations.unknownError, + null, + ); + } +} diff --git a/lib/base/helpers/directions_launcher.dart b/lib/base/helpers/directions_launcher.dart deleted file mode 100644 index 6ae5ede4..00000000 --- a/lib/base/helpers/directions_launcher.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:campus_flutter/base/classes/location.dart'; -import 'package:campus_flutter/settingsComponent/views/default_maps_picker_view.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:map_launcher/map_launcher.dart'; - -void launchDirections(Location location, String name, WidgetRef ref) async { - if (!kIsWeb) { - final selectedMap = ref.read(selectedMapsApp); - if (selectedMap != null) { - if (await MapLauncher.isMapAvailable(selectedMap.mapType) ?? false) { - await MapLauncher.showDirections( - mapType: selectedMap.mapType, - directionsMode: DirectionsMode.walking, - destinationTitle: name, - destination: Coords(location.latitude, location.longitude), - ); - } else { - _fallback(name, location); - } - } else { - _fallback(name, location); - } - } -} - -Future _fallback(String name, Location location) async { - if (await MapLauncher.isMapAvailable(MapType.google) ?? false) { - await MapLauncher.showDirections( - mapType: MapType.google, - directionsMode: DirectionsMode.walking, - destinationTitle: name, - destination: Coords(location.latitude, location.longitude), - ); - } else if (await MapLauncher.isMapAvailable(MapType.apple) ?? false) { - await MapLauncher.showDirections( - mapType: MapType.apple, - directionsMode: DirectionsMode.walking, - destinationTitle: name, - destination: Coords(location.latitude, location.longitude), - ); - } -} diff --git a/lib/base/helpers/url_launcher.dart b/lib/base/helpers/url_launcher.dart deleted file mode 100644 index 5daa4676..00000000 --- a/lib/base/helpers/url_launcher.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:io'; - -import 'package:campus_flutter/settingsComponent/views/settings_view.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - -class UrlLauncher { - static urlString(String urlString, WidgetRef ref) async { - if (kIsWeb) { - launchUrlString(urlString, mode: LaunchMode.platformDefault); - } else { - if (await canLaunchUrlString(urlString)) { - if (ref.read(useWebView)) { - launchUrlString(urlString, mode: LaunchMode.inAppBrowserView).onError( - (error, stackTrace) => launchUrlString( - urlString, - mode: LaunchMode.externalApplication, - ), - ); - } else { - launchUrlString(urlString, mode: LaunchMode.externalApplication); - } - } - } - } - - static url(Uri url, WidgetRef ref) async { - if (kIsWeb) { - launchUrl(url, mode: LaunchMode.platformDefault); - } else { - if (await canLaunchUrl(url)) { - if (ref.read(useWebView) && Platform.isIOS) { - launchUrl(url, mode: LaunchMode.inAppWebView).onError( - (error, stackTrace) => - launchUrl(url, mode: LaunchMode.externalApplication), - ); - } else { - launchUrl(url, mode: LaunchMode.externalApplication); - } - } - } - } -} diff --git a/lib/base/localization/app_de.arb b/lib/base/localization/app_de.arb index 534aad0b..d325db87 100644 --- a/lib/base/localization/app_de.arb +++ b/lib/base/localization/app_de.arb @@ -311,5 +311,18 @@ } } }, - "closest":"Nächste" -} \ No newline at end of file + "closest":"Näheste", + "noClosestStudyRoom":"Näheste Lernräume nicht ermittelbar!", + "cancel":"Abbrechen", + "selectAction":"Wähle eine Aktion", + "openIn":"In {item} öffnen", + "@openIn":{ + "description":"Item", + "placeholders":{ + "item":{ + "type":"String", + "example":"Apple Maps" + } + } + } +} diff --git a/lib/base/localization/app_en.arb b/lib/base/localization/app_en.arb index 64edb0d7..848712b5 100644 --- a/lib/base/localization/app_en.arb +++ b/lib/base/localization/app_en.arb @@ -311,5 +311,18 @@ } } }, - "closest":"Closest" -} \ No newline at end of file + "closest":"Closest", + "noClosestStudyRoom":"Closest learning spaces cannot be determined!", + "cancel":"Cancel", + "selectAction":"Select an action", + "openIn":"Open in {item}", + "@openIn":{ + "description":"Item", + "placeholders":{ + "item":{ + "type":"String", + "example":"Apple Maps" + } + } + } +} diff --git a/lib/base/networking/apis/tumdev/cache_interceptor.dart b/lib/base/networking/apis/tumdev/cache_interceptor.dart deleted file mode 100644 index d5c6fa20..00000000 --- a/lib/base/networking/apis/tumdev/cache_interceptor.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'dart:io'; - -import 'package:campus_flutter/base/networking/apis/tumdev/cached_response.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/custom_hive_cache_store.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/lru_memory_cache_store.dart'; -import 'package:flutter/foundation.dart'; -import 'package:grpc/grpc.dart'; -import 'package:protobuf/protobuf.dart'; - -class CacheInterceptor implements ClientInterceptor { - late CustomHiveCacheStore hiveCacheStore; - late LRUMemoryCacheStore memCacheStore; - - CacheInterceptor.mobileCache(Directory directory) { - hiveCacheStore = CustomHiveCacheStore( - directory.path, - ); - } - - CacheInterceptor.webCache() { - memCacheStore = LRUMemoryCacheStore(); - } - - @override - ResponseStream interceptStreaming( - ClientMethod method, - Stream requests, - CallOptions options, - ClientStreamingInvoker invoker, - ) { - return invoker(method, requests, options); - } - - @override - ResponseFuture interceptUnary( - ClientMethod method, - Q request, - CallOptions options, - ClientUnaryInvoker invoker, - ) { - final key = method.path; - final (bool, CacheResponse?) cachedResponse; - if (kIsWeb) { - cachedResponse = memCacheStore.get(key); - } else { - cachedResponse = hiveCacheStore.get(key); - } - final factory = _getFactory(); - if (cachedResponse.$1 && cachedResponse.$2 != null && factory != null) { - final data = factory(cachedResponse.$2!.data); - return ResponseFuture( - ClientCall( - ClientMethod(method.path, (value) => [], (value) => data), - Stream.value(request), - options, - null, - true, - ), - ); - } - - /// If not found in cache, invoke the actual RPC and cache the response - final response = invoker(method, request, options); - response.then((data) { - if (kIsWeb) { - memCacheStore.put(key, _convertToBuffer(data)); - } else { - hiveCacheStore.put(key, _convertToBuffer(data)); - } - }); - - return response; - } - - // TODO: figure out nicer solution or add missing classes - Uint8List? _convertToBuffer(R data) { - if (R == ListNewsReply) { - final listData = data as ListNewsReply; - return listData.writeToBuffer(); - } else if (R == ListMoviesReply) { - final movieData = data as ListMoviesReply; - return movieData.writeToBuffer(); - } else { - return null; - } - } - - // TODO: figure out nicer solution or add missing classes - R Function(List, [ExtensionRegistry])? _getFactory() { - if (R == ListNewsReply) { - return ListNewsReply.fromBuffer as R Function( - List, [ - ExtensionRegistry, - ]); - } else if (R == ListMoviesReply) { - return ListMoviesReply.fromBuffer as R Function( - List, [ - ExtensionRegistry, - ]); - } else { - return null; - } - } - - void invalidateCache() { - if (kIsWeb) { - memCacheStore.invalidate(); - } else { - hiveCacheStore.invalidate(); - } - } -} diff --git a/lib/base/networking/apis/tumdev/cached_client.dart b/lib/base/networking/apis/tumdev/cached_client.dart deleted file mode 100644 index 495e60a9..00000000 --- a/lib/base/networking/apis/tumdev/cached_client.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:campus_flutter/base/networking/apis/tumdev/cache_interceptor.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; -import 'package:flutter/foundation.dart'; -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:grpc/grpc_or_grpcweb.dart'; -import 'package:hive/hive.dart'; -import 'package:package_info_plus/package_info_plus.dart'; - -class CachedCampusClient extends CampusClient { - static Future createWebCache() async { - return CachedCampusClient._webCache(await _callOptions()); - } - - static Future createMobileCache( - Directory directory, - ) async { - await Hive.openBox('grpc_cache', path: directory.path); - return CachedCampusClient._mobileCache(directory, await _callOptions()); - } - - CachedCampusClient._webCache(CallOptions callOptions) - : super( - _channel(), - options: callOptions, - interceptors: [CacheInterceptor.webCache()], - ); - - CachedCampusClient._mobileCache(Directory directory, CallOptions callOptions) - : super( - _channel(), - options: callOptions, - interceptors: [CacheInterceptor.mobileCache(directory)], - ); - - static Future _callOptions() async { - final packageInfo = await PackageInfo.fromPlatform(); - String osVersion = "unknown"; - String deviceId = "unknown"; - DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - if (kIsWeb) { - WebBrowserInfo webBrowserInfo = await deviceInfo.webBrowserInfo; - osVersion = "web"; - deviceId = webBrowserInfo.userAgent ?? "unknown"; - } else { - if (Platform.isIOS) { - IosDeviceInfo iosInfo = await deviceInfo.iosInfo; - osVersion = "${iosInfo.systemName} ${iosInfo.systemVersion}"; - deviceId = iosInfo.identifierForVendor ?? "unknown"; - } else if (Platform.isAndroid) { - AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - osVersion = androidInfo.version.toString(); - deviceId = androidInfo.serialNumber; - } else if (Platform.isMacOS) { - MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo; - osVersion = macInfo.osRelease; - deviceId = macInfo.model; - } - } - return CallOptions( - metadata: { - "x-app-version": packageInfo.packageName, - "x-app-build": packageInfo.version, - "x-device-id": deviceId, - "x-os-version": osVersion, - }, - timeout: const Duration(seconds: 10), - ); - } - - static GrpcOrGrpcWebClientChannel _channel() { - return GrpcOrGrpcWebClientChannel.toSeparateEndpoints( - grpcHost: "api.tum.app", - grpcPort: 443, - grpcTransportSecure: true, - grpcWebHost: "api-grpc.tum.app", - grpcWebPort: 443, - grpcWebTransportSecure: true, - ); - } -} diff --git a/lib/base/networking/apis/tumdev/cached_response.dart b/lib/base/networking/apis/tumdev/cached_response.dart deleted file mode 100644 index 5bf7ba2e..00000000 --- a/lib/base/networking/apis/tumdev/cached_response.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:hive/hive.dart'; - -class CacheResponse { - DateTime saved; - List data; - - CacheResponse({required this.saved, required this.data}); -} - -class CacheResponseAdapter extends TypeAdapter { - static const int _typeId = 111; - - @override - int get typeId => _typeId; - - @override - void write(BinaryWriter writer, CacheResponse obj) { - writer - ..writeByte(2) - ..writeByte(0) - ..write(obj.saved) - ..writeByte(1) - ..write(obj.data); - } - - @override - CacheResponse read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return CacheResponse( - saved: fields[0] as DateTime, - data: (fields[1] as List).cast(), - ); - } -} diff --git a/lib/base/networking/apis/tumdev/custom_hive_cache_store.dart b/lib/base/networking/apis/tumdev/custom_hive_cache_store.dart deleted file mode 100644 index 7f465c09..00000000 --- a/lib/base/networking/apis/tumdev/custom_hive_cache_store.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:campus_flutter/base/networking/apis/tumdev/cached_response.dart'; -import 'package:campus_flutter/base/networking/base/connection_checker.dart'; -import 'package:campus_flutter/main.dart'; -import 'package:flutter/foundation.dart'; -import 'package:hive/hive.dart'; - -// TODO: rename to avoid conflicts with dio cache store -class CustomHiveCacheStore { - final String _cachePath; - final Duration ttl; - - CustomHiveCacheStore( - this._cachePath, { - this.ttl = const Duration(days: 30), - }); - - (bool, CacheResponse?) get(String key) { - final box = Hive.box('grpc_cache'); - final result = box.get(key) as CacheResponse?; - if (result == null) return (false, null); - - bool valid = false; - if (getIt().hasInternet()) { - valid = result.saved - .isAfter(DateTime.now().subtract(const Duration(minutes: 10))); - } else { - valid = result.saved.isAfter(DateTime.now().subtract(ttl)); - } - - /// clear cache entry if not valid anymore - if (!valid) { - box.delete(key); - } - - return (valid, result); - } - - void put(String key, Uint8List? data) { - if (data != null) { - final box = Hive.box('grpc_cache'); - box.put(key, CacheResponse(saved: DateTime.now(), data: data.toList())); - } - } - - void invalidate() { - Hive.box(_cachePath).clear(); - } -} diff --git a/lib/base/networking/apis/tumdev/lru_memory_cache_store.dart b/lib/base/networking/apis/tumdev/lru_memory_cache_store.dart deleted file mode 100644 index 479eb99f..00000000 --- a/lib/base/networking/apis/tumdev/lru_memory_cache_store.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:campus_flutter/base/networking/apis/tumdev/cached_response.dart'; -import 'package:flutter/foundation.dart'; - -class LRUMemoryCacheStore { - final Duration ttl; - final Map _cache = {}; - final int _maxSize = 10; - - LRUMemoryCacheStore({this.ttl = const Duration(minutes: 10)}); - - (bool, CacheResponse?) get(String key) { - final result = _cache[key]; - if (result == null) return (false, null); - - final valid = result.saved.isBefore(DateTime.now().subtract(ttl)); - if (!valid) { - _cache.remove(key); - } - return (valid, result); - } - - void put(String key, Uint8List? data) { - if (data != null) { - if (_cache.length >= _maxSize) { - _cache.remove(_cache.keys.first); - } - _cache[key] = CacheResponse(saved: DateTime.now(), data: data); - } - } - - void invalidate() { - _cache.clear(); - } -} diff --git a/lib/base/networking/base/api_response.dart b/lib/base/networking/base/api_response.dart index 4ad2843d..653e7954 100644 --- a/lib/base/networking/base/api_response.dart +++ b/lib/base/networking/base/api_response.dart @@ -1,6 +1,3 @@ -import 'package:dio/dio.dart'; -import 'package:intl/intl.dart'; - class ApiResponse { T data; DateTime? saved; @@ -9,33 +6,19 @@ class ApiResponse { factory ApiResponse.fromJson( dynamic json, - Headers headers, + Map extras, Function(Map) create, ) { if (json is List) { return ApiResponse( data: create({"data": json}), - saved: ApiResponse._parseDate(headers["date"]?.first), + saved: extras["saved"] as DateTime?, ); } else { return ApiResponse( data: create(json), - saved: ApiResponse._parseDate(headers["date"]?.first), + saved: extras["saved"] as DateTime?, ); } } - - static DateTime? _parseDate(String? dateString) { - DateFormat format = DateFormat('E, d MMM yyyy HH:mm:ss Z'); - if (dateString != null) { - try { - final today = DateTime.now(); - return format.parse(dateString).add(today.timeZoneOffset); - } catch (_) { - return null; - } - } else { - return null; - } - } } diff --git a/lib/base/networking/base/custom_cache_interceptor.dart b/lib/base/networking/base/custom_cache_interceptor.dart deleted file mode 100644 index 9cf4b6c0..00000000 --- a/lib/base/networking/base/custom_cache_interceptor.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:campus_flutter/main.dart'; -import 'package:campus_flutter/base/networking/base/connection_checker.dart'; -import 'package:dio/dio.dart'; -import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; -import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart'; - -class CustomCacheInterceptor implements Interceptor { - final HiveCacheStore cacheStore; - - final CacheOptions cacheOptions; - - CustomCacheInterceptor({ - required this.cacheStore, - required this.cacheOptions, - }); - - @override - void onError(DioException err, ErrorInterceptorHandler handler) { - handler.next(err); - } - - @override - Future onRequest( - RequestOptions options, - RequestInterceptorHandler handler, - ) async { - final hasConnection = await getIt().checkConnection(); - options.extra[CacheResponse.requestSentDate] = DateTime.now(); - final key = CacheOptions.defaultCacheKeyBuilder(options); - - /// checks if device has a working internet connection - if (hasConnection && !options.path.contains("tumCard")) { - /// if forcedRefresh parameter is passed the cache is invalidated - if (options.extra["forcedRefresh"] == "true") { - cacheStore.delete(key); - } else { - final cache = await cacheStore.get(key); - - /// device is online, fetch every 10 minutes - if (cache != null && - DateTime.now().difference(cache.responseDate).inMinutes <= 10) { - return handler.resolve(cache.toResponse(options, fromNetwork: false)); - } else { - /// if older than than 10 minutes -> invalidate cache - cacheStore.delete(key); - } - } - } else { - /// if device is offline, the cache is valid for 30 days - final cache = await cacheStore.get(key); - if (cache != null && - DateTime.now().difference(cache.responseDate).inDays <= 30) { - return handler.resolve(cache.toResponse(options, fromNetwork: false)); - } else { - cacheStore.delete(key); - } - } - handler.next(options); - } - - @override - Future onResponse( - Response response, - ResponseInterceptorHandler handler, - ) async { - final key = CacheOptions.defaultCacheKeyBuilder(response.requestOptions); - final cacheResponse = await CacheResponse.fromResponse( - key: key, - options: cacheOptions, - response: response, - ); - await cacheStore.set( - await cacheResponse.writeContent(cacheOptions, response: response), - ); - handler.next(response); - } -} diff --git a/lib/base/networking/base/grpc_client.dart b/lib/base/networking/base/grpc_client.dart new file mode 100644 index 00000000..487e51c6 --- /dev/null +++ b/lib/base/networking/base/grpc_client.dart @@ -0,0 +1,62 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:campus_flutter/base/networking/cache/grpc_cache_interceptor.dart'; +import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:grpc/grpc_or_grpcweb.dart'; +import 'package:isar/isar.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class GrpcClient extends CampusClient { + static Future createGrpcClient(Isar isar) async { + return GrpcClient(isar, await _callOptions()); + } + + GrpcClient(Isar isar, CallOptions callOptions) + : super( + _channel(), + options: callOptions, + interceptors: [GrpcCacheInterceptor(isar)], + ); + + static Future _callOptions() async { + final packageInfo = await PackageInfo.fromPlatform(); + String osVersion = "unknown"; + String deviceId = "unknown"; + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + if (Platform.isIOS) { + IosDeviceInfo iosInfo = await deviceInfo.iosInfo; + osVersion = "${iosInfo.systemName} ${iosInfo.systemVersion}"; + deviceId = iosInfo.identifierForVendor ?? "unknown"; + } else if (Platform.isAndroid) { + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + osVersion = androidInfo.version.toString(); + deviceId = androidInfo.serialNumber; + } else if (Platform.isMacOS) { + MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo; + osVersion = macInfo.osRelease; + deviceId = macInfo.model; + } + return CallOptions( + metadata: { + "x-app-version": packageInfo.packageName, + "x-app-build": packageInfo.version, + "x-device-id": deviceId, + "x-os-version": osVersion, + }, + timeout: const Duration(seconds: 10), + ); + } + + static GrpcOrGrpcWebClientChannel _channel() { + return GrpcOrGrpcWebClientChannel.toSeparateEndpoints( + grpcHost: "api.tum.app", + grpcPort: 443, + grpcTransportSecure: true, + grpcWebHost: "api-grpc.tum.app", + grpcWebPort: 443, + grpcWebTransportSecure: true, + ); + } +} diff --git a/lib/base/networking/base/rest_client.dart b/lib/base/networking/base/rest_client.dart index e3bd1fed..604b98de 100644 --- a/lib/base/networking/base/rest_client.dart +++ b/lib/base/networking/base/rest_client.dart @@ -1,80 +1,28 @@ import 'dart:convert'; import 'dart:developer'; -import 'dart:io'; +import 'package:campus_flutter/base/networking/cache/cache.dart'; +import 'package:campus_flutter/base/networking/cache/rest_cache_interceptor.dart'; import 'package:campus_flutter/base/networking/protocols/api.dart'; import 'package:campus_flutter/base/networking/protocols/api_exception.dart'; import 'package:campus_flutter/base/networking/base/api_response.dart'; -import 'package:campus_flutter/base/networking/base/custom_cache_interceptor.dart'; import 'package:dio/dio.dart'; -import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; -import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart'; -import 'package:flutter/foundation.dart'; +import 'package:isar/isar.dart'; import 'package:xml2json/xml2json.dart'; -class RESTClient { +class RestClient { late Dio dio; - late MemCacheStore memCacheStore; - late HiveCacheStore hiveCacheStore; + late Cache cache; - RESTClient.webCache() { - memCacheStore = MemCacheStore(); - - var cacheOptions = CacheOptions( - store: memCacheStore, - policy: CachePolicy.forceCache, - maxStale: const Duration(minutes: 10), - hitCacheOnErrorExcept: [401, 404], - ); - - final dio = Dio() - ..interceptors.add(DioCacheInterceptor(options: cacheOptions)); - - dio.options = BaseOptions( - responseDecoder: (data, options, body) { - final decoded = utf8.decoder.convert(data); - if (body.headers["content-type"]?.first.contains("xml") ?? false) { - final transformer = Xml2Json(); - transformer.parse(decoded); - return transformer.toParkerWithAttrs( - entries: { - "row": "rowset", - "event": "events", - "studium": "studien", - "raum": "raeume", - "gruppe": "gruppen", - "nebenstelle": "telefon_nebenstellen", - "card": "cards", - }, - ); - } else { - return decoded; - } - }, - connectTimeout: const Duration(seconds: 5), - receiveTimeout: const Duration(seconds: 5), - ); - - this.dio = dio; - } - - RESTClient.mobileCache(Directory directory) { - hiveCacheStore = HiveCacheStore(directory.path); - - /// cache duration is 30 days for offline mode - var cacheOptions = CacheOptions( - store: hiveCacheStore, - policy: CachePolicy.forceCache, - maxStale: const Duration(days: 30), - hitCacheOnErrorExcept: [401, 404], - ); + RestClient(Isar isar) { + final cache = Cache(isar: isar); + this.cache = cache; /// add custom cache interceptor to dio final dio = Dio() ..interceptors.add( - CustomCacheInterceptor( - cacheStore: hiveCacheStore, - cacheOptions: cacheOptions, + RestCacheInterceptor( + cache: cache, ), ); @@ -133,7 +81,7 @@ class RESTClient { /// check if response is error message by attempting to decoding it throw ApiResponse.fromJson( jsonDecode(response.data.toString()), - response.headers, + response.extra, createError, ).data; } on U catch (e) { @@ -145,7 +93,7 @@ class RESTClient { try { return ApiResponse.fromJson( jsonDecode(response.data.toString()), - response.headers, + response.extra, createObject, ); } catch (e) { @@ -175,7 +123,7 @@ class RESTClient { /// check if response is error message by attempting to decoding it throw ApiResponse.fromJson( jsonDecode(response.data.toString()), - response.headers, + response.extra, createError, ).data; } on U catch (e) { @@ -187,7 +135,7 @@ class RESTClient { try { return ApiResponse.fromJson( jsonDecode(response.data.toString()), - response.headers, + response.extra, createObject, ); } catch (e) { @@ -223,7 +171,7 @@ class RESTClient { return ApiResponse.fromJson( jsonDecode(response.data.toString()), - response.headers, + response.extra, createObject, ); } catch (e) { @@ -237,10 +185,6 @@ class RESTClient { } clearCache() async { - if (kIsWeb) { - memCacheStore.clean(); - } else { - hiveCacheStore.clean(); - } + await cache.resetCache(); } } diff --git a/lib/base/networking/cache/cache.dart b/lib/base/networking/cache/cache.dart new file mode 100644 index 00000000..336903b2 --- /dev/null +++ b/lib/base/networking/cache/cache.dart @@ -0,0 +1,80 @@ +import 'package:isar/isar.dart'; +import 'package:campus_flutter/base/networking/cache/cache_entry.dart'; +import 'package:campus_flutter/base/util/fast_hash.dart'; + +class Cache { + final Isar isar; + + final Duration ttl = const Duration(days: 30); + + Cache({required this.isar}); + + void add(String body, Uri uri) { + final today = DateTime.now(); + final cacheEntry = CacheEntry( + id: fastHash(uri.toString()), + url: uri.toString(), + validUntil: today.add(ttl), + saved: today, + data: body, + ); + isar.writeTxn( + () => isar.cacheEntrys.put(cacheEntry), + ); + } + + void addString(String body, String uri) { + final today = DateTime.now(); + final cacheEntry = CacheEntry( + id: fastHash(uri), + url: uri, + validUntil: today.add(ttl), + saved: today, + data: body, + ); + isar.writeTxn( + () => isar.cacheEntrys.put(cacheEntry), + ); + } + + CacheEntry? get(Uri uri) { + final hash = fastHash(uri.toString()); + final entry = isar.txnSync(() => isar.cacheEntrys.getSync(hash)); + if (entry != null) { + final today = DateTime.now(); + if (entry.validUntil.isAfter(today)) { + return entry; + } else { + isar.writeTxnSync(() => isar.cacheEntrys.deleteSync(hash)); + return null; + } + } else { + return null; + } + } + + CacheEntry? getWithString(String uri) { + final hash = fastHash(uri); + final entry = isar.txnSync(() => isar.cacheEntrys.getSync(hash)); + if (entry != null) { + final today = DateTime.now(); + if (entry.validUntil.isAfter(today)) { + return entry; + } else { + isar.writeTxnSync(() => isar.cacheEntrys.deleteSync(hash)); + return null; + } + } else { + return null; + } + } + + void delete(String key) { + final hash = fastHash(key); + isar.writeTxnSync(() => isar.cacheEntrys.deleteSync(hash)); + } + + Future resetCache() async { + return await isar.writeTxn(() => isar.clear()); + } +} diff --git a/lib/base/networking/cache/cache_entry.dart b/lib/base/networking/cache/cache_entry.dart new file mode 100644 index 00000000..1cb48900 --- /dev/null +++ b/lib/base/networking/cache/cache_entry.dart @@ -0,0 +1,20 @@ +import 'package:isar/isar.dart'; + +part 'cache_entry.g.dart'; + +@collection +class CacheEntry { + final Id id; + final String url; + final DateTime validUntil; + final DateTime saved; + final String data; + + CacheEntry({ + required this.id, + required this.url, + required this.validUntil, + required this.saved, + required this.data, + }); +} diff --git a/lib/base/networking/cache/cache_entry.g.dart b/lib/base/networking/cache/cache_entry.g.dart new file mode 100644 index 00000000..201fe995 --- /dev/null +++ b/lib/base/networking/cache/cache_entry.g.dart @@ -0,0 +1,805 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'cache_entry.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetCacheEntryCollection on Isar { + IsarCollection get cacheEntrys => this.collection(); +} + +const CacheEntrySchema = CollectionSchema( + name: r'CacheEntry', + id: 1901957776030515961, + properties: { + r'data': PropertySchema( + id: 0, + name: r'data', + type: IsarType.string, + ), + r'saved': PropertySchema( + id: 1, + name: r'saved', + type: IsarType.dateTime, + ), + r'url': PropertySchema( + id: 2, + name: r'url', + type: IsarType.string, + ), + r'validUntil': PropertySchema( + id: 3, + name: r'validUntil', + type: IsarType.dateTime, + ) + }, + estimateSize: _cacheEntryEstimateSize, + serialize: _cacheEntrySerialize, + deserialize: _cacheEntryDeserialize, + deserializeProp: _cacheEntryDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _cacheEntryGetId, + getLinks: _cacheEntryGetLinks, + attach: _cacheEntryAttach, + version: '3.1.4', +); + +int _cacheEntryEstimateSize( + CacheEntry object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.data.length * 3; + bytesCount += 3 + object.url.length * 3; + return bytesCount; +} + +void _cacheEntrySerialize( + CacheEntry object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.data); + writer.writeDateTime(offsets[1], object.saved); + writer.writeString(offsets[2], object.url); + writer.writeDateTime(offsets[3], object.validUntil); +} + +CacheEntry _cacheEntryDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = CacheEntry( + data: reader.readString(offsets[0]), + id: id, + saved: reader.readDateTime(offsets[1]), + url: reader.readString(offsets[2]), + validUntil: reader.readDateTime(offsets[3]), + ); + return object; +} + +P _cacheEntryDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readDateTime(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readDateTime(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _cacheEntryGetId(CacheEntry object) { + return object.id; +} + +List> _cacheEntryGetLinks(CacheEntry object) { + return []; +} + +void _cacheEntryAttach(IsarCollection col, Id id, CacheEntry object) {} + +extension CacheEntryQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension CacheEntryQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } +} + +extension CacheEntryQueryFilter + on QueryBuilder { + QueryBuilder dataEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'data', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'data', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'data', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'data', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'data', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'data', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'data', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'data', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dataIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'data', + value: '', + )); + }); + } + + QueryBuilder dataIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'data', + value: '', + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder savedEqualTo( + DateTime value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'saved', + value: value, + )); + }); + } + + QueryBuilder savedGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'saved', + value: value, + )); + }); + } + + QueryBuilder savedLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'saved', + value: value, + )); + }); + } + + QueryBuilder savedBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'saved', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder urlEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'url', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'url', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'url', + value: '', + )); + }); + } + + QueryBuilder urlIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'url', + value: '', + )); + }); + } + + QueryBuilder validUntilEqualTo( + DateTime value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'validUntil', + value: value, + )); + }); + } + + QueryBuilder + validUntilGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'validUntil', + value: value, + )); + }); + } + + QueryBuilder + validUntilLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'validUntil', + value: value, + )); + }); + } + + QueryBuilder validUntilBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'validUntil', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } +} + +extension CacheEntryQueryObject + on QueryBuilder {} + +extension CacheEntryQueryLinks + on QueryBuilder {} + +extension CacheEntryQuerySortBy + on QueryBuilder { + QueryBuilder sortByData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'data', Sort.asc); + }); + } + + QueryBuilder sortByDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'data', Sort.desc); + }); + } + + QueryBuilder sortBySaved() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'saved', Sort.asc); + }); + } + + QueryBuilder sortBySavedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'saved', Sort.desc); + }); + } + + QueryBuilder sortByUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.asc); + }); + } + + QueryBuilder sortByUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.desc); + }); + } + + QueryBuilder sortByValidUntil() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'validUntil', Sort.asc); + }); + } + + QueryBuilder sortByValidUntilDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'validUntil', Sort.desc); + }); + } +} + +extension CacheEntryQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'data', Sort.asc); + }); + } + + QueryBuilder thenByDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'data', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenBySaved() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'saved', Sort.asc); + }); + } + + QueryBuilder thenBySavedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'saved', Sort.desc); + }); + } + + QueryBuilder thenByUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.asc); + }); + } + + QueryBuilder thenByUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.desc); + }); + } + + QueryBuilder thenByValidUntil() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'validUntil', Sort.asc); + }); + } + + QueryBuilder thenByValidUntilDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'validUntil', Sort.desc); + }); + } +} + +extension CacheEntryQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByData( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'data', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctBySaved() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'saved'); + }); + } + + QueryBuilder distinctByUrl( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'url', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByValidUntil() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'validUntil'); + }); + } +} + +extension CacheEntryQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder dataProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'data'); + }); + } + + QueryBuilder savedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'saved'); + }); + } + + QueryBuilder urlProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'url'); + }); + } + + QueryBuilder validUntilProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'validUntil'); + }); + } +} diff --git a/lib/base/networking/cache/grpc_cache_interceptor.dart b/lib/base/networking/cache/grpc_cache_interceptor.dart new file mode 100644 index 00000000..e6f51d4a --- /dev/null +++ b/lib/base/networking/cache/grpc_cache_interceptor.dart @@ -0,0 +1,76 @@ +import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; +import 'package:campus_flutter/base/networking/cache/cache.dart'; +import 'package:grpc/grpc.dart'; +import 'package:isar/isar.dart'; +import 'package:protobuf/protobuf.dart'; + +class GrpcCacheInterceptor implements ClientInterceptor { + late Cache cache; + + GrpcCacheInterceptor(Isar isar) { + cache = Cache(isar: isar); + } + + @override + ResponseStream interceptStreaming( + ClientMethod method, + Stream requests, + CallOptions options, + ClientStreamingInvoker invoker, + ) { + return invoker(method, requests, options); + } + + @override + ResponseFuture interceptUnary( + ClientMethod method, + Q request, + CallOptions options, + ClientUnaryInvoker invoker, + ) { + final key = method.path; + final cachedResponse = cache.getWithString(key); + final factory = _getFactory(); + if (cachedResponse != null && factory != null) { + final data = factory(cachedResponse.data); + return ResponseFuture( + ClientCall( + ClientMethod(method.path, (value) => [], (value) => data), + Stream.value(request), + options, + null, + true, + ), + ); + } + + /// If not found in cache, invoke the actual RPC and cache the response + final response = invoker(method, request, options); + response.then((data) { + cache.addString((data as GeneratedMessage).writeToJson(), key); + }); + + return response; + } + + // TODO: figure out nicer solution or add missing classes + R Function(String, [ExtensionRegistry])? _getFactory() { + if (R == ListNewsReply) { + return ListNewsReply.fromJson as R Function( + String, [ + ExtensionRegistry, + ]); + } else if (R == ListMoviesReply) { + return ListMoviesReply.fromJson as R Function( + String, [ + ExtensionRegistry, + ]); + } else { + return null; + } + } + + void invalidateCache() { + cache.resetCache(); + } +} diff --git a/lib/base/networking/cache/rest_cache_interceptor.dart b/lib/base/networking/cache/rest_cache_interceptor.dart new file mode 100644 index 00000000..f103e92c --- /dev/null +++ b/lib/base/networking/cache/rest_cache_interceptor.dart @@ -0,0 +1,81 @@ +import 'package:campus_flutter/base/networking/cache/cache.dart'; +import 'package:campus_flutter/main.dart'; +import 'package:campus_flutter/base/networking/base/connection_checker.dart'; +import 'package:dio/dio.dart'; + +class RestCacheInterceptor implements Interceptor { + late Cache cache; + + RestCacheInterceptor({ + required this.cache, + }); + + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + handler.next(err); + } + + @override + Future onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + final hasConnection = await getIt().checkConnection(); + final key = options.uri.toString(); + + /// checks if device has a working internet connection + if (hasConnection && !options.path.contains("tumCard")) { + /// if forcedRefresh parameter is passed the cache is invalidated + if (options.extra["forcedRefresh"] == "true") { + cache.delete(key); + } else { + final cacheEntry = cache.getWithString(key); + + /// device is online, fetch every 10 minutes + if (cacheEntry != null && + DateTime.now().difference(cacheEntry.saved).inMinutes <= 10) { + return handler.resolve( + Response( + data: cacheEntry.data, + extra: { + "saved": cacheEntry.saved, + }, + statusCode: 304, + requestOptions: options, + ), + ); + } else { + /// if older than than 10 minutes -> invalidate cache + cache.delete(key); + } + } + } else { + /// if device is offline, the cache is valid for 30 days + final cacheEntry = cache.getWithString(key); + if (cacheEntry != null && + DateTime.now().difference(cacheEntry.saved).inDays <= 30) { + return handler.resolve( + Response( + data: cacheEntry.data, + extra: { + "saved": cacheEntry.saved, + }, + statusCode: 304, + requestOptions: options, + ), + ); + } else { + cache.delete(key); + } + } + handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + final key = response.realUri.toString(); + cache.addString(response.data, key); + response.extra = response.extra..addAll({"saved": DateTime.now()}); + handler.next(response); + } +} diff --git a/lib/base/routing/router.dart b/lib/base/routing/router.dart index f87e86bf..620c595a 100644 --- a/lib/base/routing/router.dart +++ b/lib/base/routing/router.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/campus.dart'; -import 'package:campus_flutter/base/helpers/fullscreen_image_view.dart'; +import 'package:campus_flutter/base/util/fullscreen_image_view.dart'; import 'package:campus_flutter/base/routing/router_service.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; diff --git a/lib/base/services/location_service.dart b/lib/base/services/location_service.dart index 8a099d9a..87150a56 100644 --- a/lib/base/services/location_service.dart +++ b/lib/base/services/location_service.dart @@ -1,5 +1,4 @@ import 'package:geolocator/geolocator.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; /// Determine the current position of the device. /// @@ -31,10 +30,6 @@ class LocationService { } static Future getLastKnown() async { - if (kIsWeb) { - return await determinePosition(); - } else { - return await Geolocator.getLastKnownPosition(); - } + return await Geolocator.getLastKnownPosition(); } } diff --git a/lib/base/helpers/card_with_padding.dart b/lib/base/util/card_with_padding.dart similarity index 100% rename from lib/base/helpers/card_with_padding.dart rename to lib/base/util/card_with_padding.dart diff --git a/lib/base/helpers/custom_back_button.dart b/lib/base/util/custom_back_button.dart similarity index 100% rename from lib/base/helpers/custom_back_button.dart rename to lib/base/util/custom_back_button.dart diff --git a/lib/base/helpers/delayed_loading_indicator.dart b/lib/base/util/delayed_loading_indicator.dart similarity index 100% rename from lib/base/helpers/delayed_loading_indicator.dart rename to lib/base/util/delayed_loading_indicator.dart diff --git a/lib/base/util/directions_launcher.dart b/lib/base/util/directions_launcher.dart new file mode 100644 index 00000000..e4e9a8a1 --- /dev/null +++ b/lib/base/util/directions_launcher.dart @@ -0,0 +1,72 @@ +import 'package:campus_flutter/base/classes/location.dart'; +import 'package:campus_flutter/base/extensions/context.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:map_launcher/map_launcher.dart'; + +Future showDirectionsDialog( + String name, + Location location, + BuildContext context, +) async { + MapLauncher.installedMaps.then( + (value) => showModalBottomSheet( + isDismissible: true, + showDragHandle: true, + context: context, + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only( + bottom: context.halfPadding, + ), + child: Text(context.localizations.selectAction), + ), + Card( + child: ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsets.zero, + itemBuilder: (context, index) => ListTile( + leading: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: SvgPicture.asset( + value[index].icon, + height: 30, + width: 30, + ), + ), + title: Text( + context.localizations.openIn(value[index].mapName), + ), + onTap: () => MapLauncher.showDirections( + mapType: value[index].mapType, + directionsMode: DirectionsMode.walking, + destinationTitle: name, + destination: Coords(location.latitude, location.longitude), + ), + ), + separatorBuilder: (context, index) => const PaddedDivider(), + itemCount: value.length, + ), + ), + Padding( + padding: EdgeInsets.only( + top: context.padding, + bottom: context.padding * 2, + ), + child: ElevatedButton( + onPressed: () => context.pop(), + child: Text(context.localizations.cancel), + ), + ), + ], + ); + }, + ), + ); +} diff --git a/lib/base/helpers/enum_parser.dart b/lib/base/util/enum_parser.dart similarity index 100% rename from lib/base/helpers/enum_parser.dart rename to lib/base/util/enum_parser.dart diff --git a/lib/base/util/fast_hash.dart b/lib/base/util/fast_hash.dart new file mode 100644 index 00000000..73c9cb9c --- /dev/null +++ b/lib/base/util/fast_hash.dart @@ -0,0 +1,15 @@ +/// as found on https://isar.dev/recipes/string_ids.html#fast-hash-function +int fastHash(String string) { + var hash = 0xcbf29ce484222325; + + var i = 0; + while (i < string.length) { + final codeUnit = string.codeUnitAt(i++); + hash ^= codeUnit >> 8; + hash *= 0x100000001b3; + hash ^= codeUnit & 0xFF; + hash *= 0x100000001b3; + } + + return hash; +} diff --git a/lib/base/helpers/fullscreen_image_view.dart b/lib/base/util/fullscreen_image_view.dart similarity index 96% rename from lib/base/helpers/fullscreen_image_view.dart rename to lib/base/util/fullscreen_image_view.dart index f392de07..b4acb803 100644 --- a/lib/base/helpers/fullscreen_image_view.dart +++ b/lib/base/util/fullscreen_image_view.dart @@ -2,10 +2,9 @@ import 'dart:async'; import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/navigaTumComponent/model/navigatum_roomfinder_map.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class ImageFullScreenScaffold extends StatelessWidget { @@ -233,12 +232,8 @@ class _ImageFullScreenViewState extends State { } double _getMinusRadius() { - if (!kIsWeb) { - if (Platform.isIOS) { - return radius; - } else { - return 0; - } + if (Platform.isIOS) { + return radius; } else { return 0; } diff --git a/lib/base/helpers/horizontal_slider.dart b/lib/base/util/horizontal_slider.dart similarity index 100% rename from lib/base/helpers/horizontal_slider.dart rename to lib/base/util/horizontal_slider.dart diff --git a/lib/base/helpers/hyperlink_text.dart b/lib/base/util/hyperlink_text.dart similarity index 95% rename from lib/base/helpers/hyperlink_text.dart rename to lib/base/util/hyperlink_text.dart index ee5a01ef..6ecb27ad 100644 --- a/lib/base/helpers/hyperlink_text.dart +++ b/lib/base/util/hyperlink_text.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/url_launcher.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/base/helpers/icon_text.dart b/lib/base/util/icon_text.dart similarity index 100% rename from lib/base/helpers/icon_text.dart rename to lib/base/util/icon_text.dart diff --git a/lib/base/helpers/info_row.dart b/lib/base/util/info_row.dart similarity index 76% rename from lib/base/helpers/info_row.dart rename to lib/base/util/info_row.dart index 5820d5fa..5ff658a9 100644 --- a/lib/base/helpers/info_row.dart +++ b/lib/base/util/info_row.dart @@ -1,3 +1,4 @@ +import 'package:campus_flutter/base/extensions/context.dart'; import 'package:flutter/material.dart'; class InfoRow extends StatelessWidget { @@ -14,6 +15,7 @@ class InfoRow extends StatelessWidget { child: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)), ), + Padding(padding: EdgeInsets.symmetric(horizontal: context.halfPadding)), Expanded(child: Text(info)), ], ); diff --git a/lib/base/helpers/last_updated_text.dart b/lib/base/util/last_updated_text.dart similarity index 100% rename from lib/base/helpers/last_updated_text.dart rename to lib/base/util/last_updated_text.dart diff --git a/lib/base/helpers/padded_divider.dart b/lib/base/util/padded_divider.dart similarity index 100% rename from lib/base/helpers/padded_divider.dart rename to lib/base/util/padded_divider.dart diff --git a/lib/base/helpers/placeholder_text.dart b/lib/base/util/placeholder_text.dart similarity index 91% rename from lib/base/helpers/placeholder_text.dart rename to lib/base/util/placeholder_text.dart index aa39b717..782b2fe6 100644 --- a/lib/base/helpers/placeholder_text.dart +++ b/lib/base/util/placeholder_text.dart @@ -21,7 +21,7 @@ class PlaceholderText extends StatelessWidget { final TextPainter textPainter = TextPainter( text: TextSpan(text: text, style: style), textDirection: TextDirection.ltr, - textScaleFactor: MediaQuery.textScaleFactorOf(context), + textScaler: MediaQuery.textScalerOf(context), )..layout(); return textPainter.size; } diff --git a/lib/base/helpers/read_list_value.dart b/lib/base/util/read_list_value.dart similarity index 100% rename from lib/base/helpers/read_list_value.dart rename to lib/base/util/read_list_value.dart diff --git a/lib/base/helpers/semester_calculator.dart b/lib/base/util/semester_calculator.dart similarity index 100% rename from lib/base/helpers/semester_calculator.dart rename to lib/base/util/semester_calculator.dart diff --git a/lib/base/helpers/shimmer_view.dart b/lib/base/util/shimmer_view.dart similarity index 100% rename from lib/base/helpers/shimmer_view.dart rename to lib/base/util/shimmer_view.dart diff --git a/lib/base/helpers/string_parser.dart b/lib/base/util/string_parser.dart similarity index 90% rename from lib/base/helpers/string_parser.dart rename to lib/base/util/string_parser.dart index 602f7859..dd716931 100644 --- a/lib/base/helpers/string_parser.dart +++ b/lib/base/util/string_parser.dart @@ -39,13 +39,10 @@ class StringParser { case "42": return "M.Ed."; case "53": - // TODO: what does it stand for? return "MBD"; case "60": - // TODO: what does it stand for? return "BECE"; case "61": - // TODO: what does it stand for? return "BEEDE"; default: return ""; @@ -53,14 +50,22 @@ class StringParser { } static String degreeShort(String degree, BuildContext context) { - // TODO: + // TODO: add missing degrees switch (degree) { case "Bachelor of Science": return "B.Sc."; case "Master of Science": return "M.Sc."; + case "Bachelor of Arts": + return "B.A."; + case "Master of Arts": + return "M.A."; + case "Bachelor of Education": + return "B.Ed."; + case "Master of Education": + return "M.Ed."; default: - return context.localizations.unknown; + return ""; } } diff --git a/lib/base/util/url_launcher.dart b/lib/base/util/url_launcher.dart new file mode 100644 index 00000000..fb2e96be --- /dev/null +++ b/lib/base/util/url_launcher.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:campus_flutter/settingsComponent/views/settings_view.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class UrlLauncher { + static urlString(String urlString, WidgetRef ref) async { + if (await canLaunchUrlString(urlString)) { + if (ref.read(useWebView)) { + launchUrlString(urlString, mode: LaunchMode.inAppBrowserView).onError( + (error, stackTrace) => launchUrlString( + urlString, + mode: LaunchMode.externalApplication, + ), + ); + } else { + launchUrlString(urlString, mode: LaunchMode.externalApplication); + } + } + } + + static url(Uri url, WidgetRef ref) async { + if (await canLaunchUrl(url)) { + if (ref.read(useWebView) && Platform.isIOS) { + launchUrl(url, mode: LaunchMode.inAppWebView).onError( + (error, stackTrace) => + launchUrl(url, mode: LaunchMode.externalApplication), + ); + } else { + launchUrl(url, mode: LaunchMode.externalApplication); + } + } + } +} diff --git a/lib/base/views/seperated_list.dart b/lib/base/views/seperated_list.dart index e4a0f6f9..e2776811 100644 --- a/lib/base/views/seperated_list.dart +++ b/lib/base/views/seperated_list.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:flutter/material.dart'; class SeparatedList extends StatelessWidget { diff --git a/lib/calendarComponent/services/calendar_service.dart b/lib/calendarComponent/services/calendar_service.dart index fc69806c..4ac97499 100644 --- a/lib/calendarComponent/services/calendar_service.dart +++ b/lib/calendarComponent/services/calendar_service.dart @@ -10,7 +10,7 @@ class CalendarService { static Future<(DateTime?, List)> fetchCalendar( bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServiceCalendar()), @@ -24,7 +24,7 @@ class CalendarService { static Future createCalendarEvent( AddedCalendarEvent addedCalendarEvent, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.getWithException< CalendarCreationConfirmationData, TumOnlineApi, TumOnlineApiException>( TumOnlineApi( @@ -45,7 +45,7 @@ class CalendarService { static Future deleteCalendarEvent( String id, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); restClient.getWithException( TumOnlineApi( TumOnlineServiceEventDelete( diff --git a/lib/calendarComponent/views/calendars_view.dart b/lib/calendarComponent/views/calendars_view.dart index 59d85ce7..744ccc20 100644 --- a/lib/calendarComponent/views/calendars_view.dart +++ b/lib/calendarComponent/views/calendars_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/last_updated_text.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/last_updated_text.dart'; import 'package:campus_flutter/calendarComponent/viewModels/calendar_viewmodel.dart'; import 'package:campus_flutter/calendarComponent/views/calendar_day_view.dart'; import 'package:campus_flutter/calendarComponent/views/calendar_month_view.dart'; diff --git a/lib/calendarComponent/views/event_creation_view.dart b/lib/calendarComponent/views/event_creation_view.dart index 2a9e5f85..aefb5e2c 100644 --- a/lib/calendarComponent/views/event_creation_view.dart +++ b/lib/calendarComponent/views/event_creation_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; import 'package:campus_flutter/calendarComponent/viewModels/calendar_addition_viewmodel.dart'; import 'package:campus_flutter/calendarComponent/views/event_creation_date_time_picker.dart'; diff --git a/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart b/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart index 3ae6642e..347b491e 100644 --- a/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart +++ b/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; import 'package:campus_flutter/calendarComponent/viewModels/calendar_viewmodel.dart'; import 'package:campus_flutter/calendarComponent/views/homeWidget/calendar_widget_event_view.dart'; diff --git a/lib/departuresComponent/model/departure.dart b/lib/departuresComponent/model/departure.dart index 0407dfe9..78420622 100644 --- a/lib/departuresComponent/model/departure.dart +++ b/lib/departuresComponent/model/departure.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/departuresComponent/model/mvv_response.dart b/lib/departuresComponent/model/mvv_response.dart index 265f4607..d3882fed 100644 --- a/lib/departuresComponent/model/mvv_response.dart +++ b/lib/departuresComponent/model/mvv_response.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/read_list_value.dart'; +import 'package:campus_flutter/base/util/read_list_value.dart'; import 'package:campus_flutter/departuresComponent/model/departure.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/departuresComponent/services/departures_service.dart b/lib/departuresComponent/services/departures_service.dart index 36409204..96ab3dee 100644 --- a/lib/departuresComponent/services/departures_service.dart +++ b/lib/departuresComponent/services/departures_service.dart @@ -9,7 +9,7 @@ class DeparturesService { String station, int? walkingTime, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.get( MvvDeparturesApi(station: station, walkingTime: walkingTime), MvvResponse.fromJson, diff --git a/lib/departuresComponent/viewModel/departures_viewmodel.dart b/lib/departuresComponent/viewModel/departures_viewmodel.dart index 67d782af..26a55878 100644 --- a/lib/departuresComponent/viewModel/departures_viewmodel.dart +++ b/lib/departuresComponent/viewModel/departures_viewmodel.dart @@ -4,7 +4,7 @@ import 'dart:convert'; import 'package:campus_flutter/base/enums/campus.dart'; import 'package:campus_flutter/base/enums/user_preference.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/base/services/location_service.dart'; import 'package:campus_flutter/departuresComponent/model/departure.dart'; import 'package:campus_flutter/departuresComponent/model/departures_preference.dart'; diff --git a/lib/departuresComponent/views/departures_details_row_view.dart b/lib/departuresComponent/views/departures_details_row_view.dart index b52e926a..b22f38a5 100644 --- a/lib/departuresComponent/views/departures_details_row_view.dart +++ b/lib/departuresComponent/views/departures_details_row_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/url_launcher.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:campus_flutter/departuresComponent/model/departure.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/departuresComponent/views/departures_details_view.dart b/lib/departuresComponent/views/departures_details_view.dart index 5224ff91..05c05c4d 100644 --- a/lib/departuresComponent/views/departures_details_view.dart +++ b/lib/departuresComponent/views/departures_details_view.dart @@ -1,10 +1,10 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/directions_launcher.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; -import 'package:campus_flutter/base/helpers/last_updated_text.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/directions_launcher.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; +import 'package:campus_flutter/base/util/last_updated_text.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/departuresComponent/model/departure.dart'; import 'package:campus_flutter/departuresComponent/model/station.dart'; @@ -111,10 +111,10 @@ class _DeparturesDetailsViewState extends ConsumerState { ref.read(departureViewModel).selectedStation.value; if (selectedStation != null && selectedStation.location != null) { - launchDirections( - selectedStation.location!, + showDirectionsDialog( selectedStation.name, - ref, + selectedStation.location!, + context, ); } }, diff --git a/lib/departuresComponent/views/homeWidget/departures_widget_view.dart b/lib/departuresComponent/views/homeWidget/departures_widget_view.dart index 0953173b..ee716970 100644 --- a/lib/departuresComponent/views/homeWidget/departures_widget_view.dart +++ b/lib/departuresComponent/views/homeWidget/departures_widget_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/campus.dart'; import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/departuresComponent/model/departure.dart'; diff --git a/lib/feedbackComponent/services/feedback_service.dart b/lib/feedbackComponent/services/feedback_service.dart index 1526c81f..139777f8 100644 --- a/lib/feedbackComponent/services/feedback_service.dart +++ b/lib/feedbackComponent/services/feedback_service.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:campus_flutter/base/networking/apis/tumdev/cached_client.dart'; +import 'package:campus_flutter/base/networking/base/grpc_client.dart'; import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; import 'package:campus_flutter/main.dart'; @@ -8,7 +8,7 @@ class FeedbackService { static Future sendFeedback( CreateFeedbackRequest createFeedbackRequest, ) async { - CachedCampusClient restClient = getIt(); + GrpcClient restClient = getIt(); return await restClient.createFeedback(Stream.value(createFeedbackRequest)); } } diff --git a/lib/feedbackComponent/views/feedback_form_view.dart b/lib/feedbackComponent/views/feedback_form_view.dart index d449d6db..73ee0ec1 100644 --- a/lib/feedbackComponent/views/feedback_form_view.dart +++ b/lib/feedbackComponent/views/feedback_form_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/extensions/context.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/feedbackComponent/viewModels/feedback_viewmodel.dart'; import 'package:campus_flutter/feedbackComponent/views/feedback_checkmark_view.dart'; @@ -112,7 +112,7 @@ class _FeedbackFormViewState extends ConsumerState { ), Padding( padding: EdgeInsets.symmetric( - vertical: context.halfPadding, + vertical: context.halfPadding / 2, ), ), ErrorHandlingRouter( diff --git a/lib/feedbackComponent/views/feedback_textfield.dart b/lib/feedbackComponent/views/feedback_textfield.dart index fb885088..4a5eb43d 100644 --- a/lib/feedbackComponent/views/feedback_textfield.dart +++ b/lib/feedbackComponent/views/feedback_textfield.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/gradeComponent/model/average_grade.dart b/lib/gradeComponent/model/average_grade.dart index cc719df9..3dea35a7 100644 --- a/lib/gradeComponent/model/average_grade.dart +++ b/lib/gradeComponent/model/average_grade.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:json_annotation/json_annotation.dart'; part 'average_grade.g.dart'; diff --git a/lib/gradeComponent/services/grade_service.dart b/lib/gradeComponent/services/grade_service.dart index 69227d12..146a990e 100644 --- a/lib/gradeComponent/services/grade_service.dart +++ b/lib/gradeComponent/services/grade_service.dart @@ -10,7 +10,7 @@ class GradeService { static Future<({DateTime? saved, List data})> fetchGrades( bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServicePersonalGrades()), @@ -23,7 +23,7 @@ class GradeService { static Future<({DateTime? saved, List data})> fetchAverageGrades(bool forcedRefresh) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServiceAverageGrades()), diff --git a/lib/gradeComponent/viewModels/grade_viewmodel.dart b/lib/gradeComponent/viewModels/grade_viewmodel.dart index 219c57b0..00f6642d 100644 --- a/lib/gradeComponent/viewModels/grade_viewmodel.dart +++ b/lib/gradeComponent/viewModels/grade_viewmodel.dart @@ -1,5 +1,5 @@ -import 'package:campus_flutter/base/helpers/icon_text.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/gradeComponent/model/average_grade.dart'; import 'package:campus_flutter/gradeComponent/model/grade.dart'; import 'package:campus_flutter/gradeComponent/services/grade_service.dart'; diff --git a/lib/gradeComponent/views/chart_view.dart b/lib/gradeComponent/views/chart_view.dart index 50eb5cb4..c637fd0a 100644 --- a/lib/gradeComponent/views/chart_view.dart +++ b/lib/gradeComponent/views/chart_view.dart @@ -1,8 +1,8 @@ import 'dart:math'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/gradeComponent/viewModels/grade_viewmodel.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/gradeComponent/views/grade_rectangle.dart b/lib/gradeComponent/views/grade_rectangle.dart index 6aabdad3..26a9de45 100644 --- a/lib/gradeComponent/views/grade_rectangle.dart +++ b/lib/gradeComponent/views/grade_rectangle.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/gradeComponent/viewModels/grade_viewmodel.dart'; import 'package:flutter/material.dart'; diff --git a/lib/gradeComponent/views/grade_view.dart b/lib/gradeComponent/views/grade_view.dart index 874af2d0..f22bf763 100644 --- a/lib/gradeComponent/views/grade_view.dart +++ b/lib/gradeComponent/views/grade_view.dart @@ -1,6 +1,6 @@ import 'dart:core'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/gradeComponent/model/grade.dart'; import 'package:campus_flutter/gradeComponent/views/grade_rectangle.dart'; import 'package:flutter/material.dart'; diff --git a/lib/gradeComponent/views/grades_view.dart b/lib/gradeComponent/views/grades_view.dart index 9edb2e33..8249f335 100644 --- a/lib/gradeComponent/views/grades_view.dart +++ b/lib/gradeComponent/views/grades_view.dart @@ -1,10 +1,10 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/last_updated_text.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; -import 'package:campus_flutter/base/helpers/semester_calculator.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/last_updated_text.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; +import 'package:campus_flutter/base/util/semester_calculator.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/gradeComponent/model/grade.dart'; import 'package:campus_flutter/gradeComponent/viewModels/grade_viewmodel.dart'; diff --git a/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart b/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart index ef6b4392..34d96f81 100644 --- a/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart +++ b/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart @@ -1,5 +1,5 @@ -import 'package:campus_flutter/base/helpers/placeholder_text.dart'; -import 'package:campus_flutter/base/helpers/shimmer_view.dart'; +import 'package:campus_flutter/base/util/placeholder_text.dart'; +import 'package:campus_flutter/base/util/shimmer_view.dart'; import 'package:flutter/material.dart'; class ContactCardLoadingView extends StatelessWidget { diff --git a/lib/homeComponent/contactComponent/views/contact_card_view.dart b/lib/homeComponent/contactComponent/views/contact_card_view.dart index 8074d10a..b6705a77 100644 --- a/lib/homeComponent/contactComponent/views/contact_card_view.dart +++ b/lib/homeComponent/contactComponent/views/contact_card_view.dart @@ -1,7 +1,8 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:campus_flutter/base/extensions/base_64_decode_image_data.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/homeComponent/contactComponent/views/contact_card_loading_view.dart'; +import 'package:campus_flutter/navigation_service.dart'; import 'package:campus_flutter/personDetailedComponent/model/person_details.dart'; import 'package:campus_flutter/personDetailedComponent/viewModel/person_details_viewmodel.dart'; import 'package:campus_flutter/profileComponent/model/profile.dart'; @@ -33,9 +34,12 @@ class _ContactCardViewState extends ConsumerState { stream: ref.watch(profileDetailsViewModel).personDetails, builder: (context, snapshot) { if (snapshot.hasData || snapshot.hasError) { - return contactInfo( - snapshot.data, - ref.read(profileViewModel).profile.value!, + return InkWell( + onTap: () => NavigationService.openStudentCardSheet(context), + child: contactInfo( + snapshot.data, + ref.read(profileViewModel).profile.value!, + ), ); } else { return DelayedLoadingIndicator( diff --git a/lib/homeComponent/contactComponent/views/contact_view.dart b/lib/homeComponent/contactComponent/views/contact_view.dart index bec6794a..cab25857 100644 --- a/lib/homeComponent/contactComponent/views/contact_view.dart +++ b/lib/homeComponent/contactComponent/views/contact_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; import 'package:campus_flutter/homeComponent/contactComponent/views/contact_card_view.dart'; import 'package:campus_flutter/homeComponent/contactComponent/views/link_view.dart'; import 'package:campus_flutter/homeComponent/contactComponent/views/tuition_view.dart'; diff --git a/lib/homeComponent/contactComponent/views/link_view.dart b/lib/homeComponent/contactComponent/views/link_view.dart index 499f5cee..ef85124b 100644 --- a/lib/homeComponent/contactComponent/views/link_view.dart +++ b/lib/homeComponent/contactComponent/views/link_view.dart @@ -1,8 +1,9 @@ import 'package:auto_size_text/auto_size_text.dart'; -import 'package:campus_flutter/base/helpers/url_launcher.dart'; import 'package:campus_flutter/base/extensions/context.dart'; +import 'package:campus_flutter/base/routing/routes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; class LinkView extends ConsumerWidget { const LinkView({super.key}); @@ -21,13 +22,13 @@ class LinkView extends ConsumerWidget { ), child: ListTile( leading: const Icon(Icons.school), - title: const Center( + title: Center( child: AutoSizeText( - "Moodle", + context.localizations.studyRooms, maxLines: 1, ), ), - onTap: () => UrlLauncher.urlString("https://moodle.tum.de", ref), + onTap: () => context.push(studyRooms), ), ), ), @@ -40,14 +41,14 @@ class LinkView extends ConsumerWidget { left: context.halfPadding, ), child: ListTile( - leading: const Icon(Icons.person), - title: const Center( + leading: const Icon(Icons.restaurant), + title: Center( child: AutoSizeText( - "TUMonline", + context.localizations.cafeterias, maxLines: 1, ), ), - onTap: () => UrlLauncher.urlString("https://campus.tum.de", ref), + onTap: () => context.push(cafeterias), ), ), ), diff --git a/lib/homeComponent/contactComponent/views/tuition_view.dart b/lib/homeComponent/contactComponent/views/tuition_view.dart index 3e40cf87..be68342a 100644 --- a/lib/homeComponent/contactComponent/views/tuition_view.dart +++ b/lib/homeComponent/contactComponent/views/tuition_view.dart @@ -1,6 +1,6 @@ -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; -import 'package:campus_flutter/base/helpers/info_row.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; +import 'package:campus_flutter/base/util/info_row.dart'; import 'package:campus_flutter/profileComponent/model/tuition.dart'; import 'package:campus_flutter/profileComponent/viewModel/profile_viewmodel.dart'; import 'package:flutter/material.dart'; diff --git a/lib/homeComponent/home_screen.dart b/lib/homeComponent/home_screen.dart index f2be8333..ef90b4ca 100644 --- a/lib/homeComponent/home_screen.dart +++ b/lib/homeComponent/home_screen.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:campus_flutter/homeComponent/contactComponent/views/contact_view.dart'; import 'package:campus_flutter/homeComponent/split_view_viewmodel.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_screen.dart'; diff --git a/lib/homeComponent/widgetComponent/views/preference_selection_view.dart b/lib/homeComponent/widgetComponent/views/preference_selection_view.dart index e7ab97ce..f130e825 100644 --- a/lib/homeComponent/widgetComponent/views/preference_selection_view.dart +++ b/lib/homeComponent/widgetComponent/views/preference_selection_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:flutter/material.dart'; class PreferenceSelectionView extends StatelessWidget { diff --git a/lib/homeComponent/widgetComponent/views/widget_screen.dart b/lib/homeComponent/widgetComponent/views/widget_screen.dart index 54eff07f..7f13aee5 100644 --- a/lib/homeComponent/widgetComponent/views/widget_screen.dart +++ b/lib/homeComponent/widgetComponent/views/widget_screen.dart @@ -1,6 +1,6 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/enums/home_widget.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/viewModels/home_viewmodel.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/home_settings_view.dart'; @@ -30,7 +30,6 @@ class WidgetScreen extends ConsumerWidget { if (snapshot.hasData) { return Column( children: [ - // TODO: hide movies and news if empty for (var widget in snapshot.data ?? []) if (widget.enabled) HomeViewModel.getWidget(widget.widgetType), ], diff --git a/lib/lectureComponent/model/lecture_details.dart b/lib/lectureComponent/model/lecture_details.dart index 7c561c52..4927f87b 100644 --- a/lib/lectureComponent/model/lecture_details.dart +++ b/lib/lectureComponent/model/lecture_details.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:flutter/widgets.dart'; import 'package:campus_flutter/base/extensions/context.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/lectureComponent/services/lecture_details_service.dart b/lib/lectureComponent/services/lecture_details_service.dart index dff40047..4be897f0 100644 --- a/lib/lectureComponent/services/lecture_details_service.dart +++ b/lib/lectureComponent/services/lecture_details_service.dart @@ -10,7 +10,7 @@ class LectureDetailsService { String lvNumber, bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.getWithException( TumOnlineApi(TumOnlineServiceLectureDetails(lvNr: lvNumber)), diff --git a/lib/lectureComponent/services/lecture_search_service.dart b/lib/lectureComponent/services/lecture_search_service.dart index 0dd678fa..35ec1460 100644 --- a/lib/lectureComponent/services/lecture_search_service.dart +++ b/lib/lectureComponent/services/lecture_search_service.dart @@ -10,7 +10,7 @@ class LectureSearchService { bool forcedRefresh, String query, ) async { - final response = await getIt() + final response = await getIt() .getWithException( TumOnlineApi(TumOnlineServiceLectureSearch(search: query)), Lectures.fromJson, diff --git a/lib/lectureComponent/services/lecture_service.dart b/lib/lectureComponent/services/lecture_service.dart index 2041d77e..e0ba82dd 100644 --- a/lib/lectureComponent/services/lecture_service.dart +++ b/lib/lectureComponent/services/lecture_service.dart @@ -9,7 +9,7 @@ class LectureService { static Future<(DateTime?, List)> fetchLecture( bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServicePersonalLectures()), diff --git a/lib/lectureComponent/views/detailed_lecture_info_row_view.dart b/lib/lectureComponent/views/detailed_lecture_info_row_view.dart index d2827bb4..121ece8a 100644 --- a/lib/lectureComponent/views/detailed_lecture_info_row_view.dart +++ b/lib/lectureComponent/views/detailed_lecture_info_row_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/url_launcher.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:flutter/material.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/lectureComponent/views/lecture_details_view.dart b/lib/lectureComponent/views/lecture_details_view.dart index 1023ff2c..9c94042e 100644 --- a/lib/lectureComponent/views/lecture_details_view.dart +++ b/lib/lectureComponent/views/lecture_details_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/last_updated_text.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/last_updated_text.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; import 'package:campus_flutter/lectureComponent/model/lecture.dart'; diff --git a/lib/lectureComponent/views/lecture_info_card_view.dart b/lib/lectureComponent/views/lecture_info_card_view.dart index bc3223ee..3d3ff400 100644 --- a/lib/lectureComponent/views/lecture_info_card_view.dart +++ b/lib/lectureComponent/views/lecture_info_card_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:flutter/material.dart'; diff --git a/lib/lectureComponent/views/lecture_links_view.dart b/lib/lectureComponent/views/lecture_links_view.dart index 3c455e94..152385cd 100644 --- a/lib/lectureComponent/views/lecture_links_view.dart +++ b/lib/lectureComponent/views/lecture_links_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/hyperlink_text.dart'; +import 'package:campus_flutter/base/util/hyperlink_text.dart'; import 'package:campus_flutter/lectureComponent/model/lecture_details.dart'; import 'package:campus_flutter/lectureComponent/views/lecture_info_card_view.dart'; import 'package:campus_flutter/base/extensions/context.dart'; diff --git a/lib/lectureComponent/views/lecture_view.dart b/lib/lectureComponent/views/lecture_view.dart index 73485d9c..a18745d6 100644 --- a/lib/lectureComponent/views/lecture_view.dart +++ b/lib/lectureComponent/views/lecture_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/homeComponent/split_view_viewmodel.dart'; import 'package:campus_flutter/lectureComponent/model/lecture.dart'; diff --git a/lib/lectureComponent/views/lectures_view.dart b/lib/lectureComponent/views/lectures_view.dart index e636282c..64558233 100644 --- a/lib/lectureComponent/views/lectures_view.dart +++ b/lib/lectureComponent/views/lectures_view.dart @@ -1,9 +1,9 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/last_updated_text.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/semester_calculator.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/last_updated_text.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/semester_calculator.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/homeComponent/split_view_viewmodel.dart'; import 'package:campus_flutter/lectureComponent/model/lecture.dart'; diff --git a/lib/main.dart b/lib/main.dart index 6231afc3..3e646fee 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,9 +2,8 @@ import 'dart:io'; import 'package:campus_flutter/base/enums/appearance.dart'; import 'package:campus_flutter/base/enums/shortcut_item.dart'; -import 'package:campus_flutter/base/helpers/enum_parser.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/cached_client.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/cached_response.dart'; +import 'package:campus_flutter/base/util/enum_parser.dart'; +import 'package:campus_flutter/base/networking/base/grpc_client.dart'; import 'package:campus_flutter/base/networking/base/connection_checker.dart'; import 'package:campus_flutter/base/networking/base/rest_client.dart'; import 'package:campus_flutter/base/routing/router.dart'; @@ -22,8 +21,7 @@ import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get_it/get_it.dart'; import 'package:home_widget/home_widget.dart'; -import 'package:map_launcher/map_launcher.dart'; -import 'package:hive/hive.dart'; +import 'package:isar/isar.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -32,6 +30,8 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:quick_actions/quick_actions.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'base/networking/cache/cache_entry.dart'; + final getIt = GetIt.instance; final customLocale = StateProvider((ref) => null); final appearance = StateProvider((ref) => Appearance.system); @@ -40,12 +40,8 @@ main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); await _initializeFirebase(); - await _initializeGeneral(); - if (kIsWeb) { - await _initializeWeb(); - } else { - await _initializeMobile(); - } + await _initializeNetworkingClients(); + await _initializeServices(); runApp( ProviderScope( child: CampusApp(launchedFromWidget: await _initializeHomeWidgets()), @@ -54,7 +50,7 @@ main() async { } Future _initializeFirebase() async { - if (!kDebugMode && !kIsWeb) { + if (!kDebugMode) { await Firebase.initializeApp(); FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; PlatformDispatcher.instance.onError = (error, stack) { @@ -64,7 +60,19 @@ Future _initializeFirebase() async { } } -Future _initializeGeneral() async { +Future _initializeNetworkingClients() async { + final directory = await getApplicationDocumentsDirectory(); + final isar = await Isar.open( + [ + CacheEntrySchema, + ], + directory: directory.path, + ); + getIt.registerSingleton(RestClient(isar)); + getIt.registerSingleton(await GrpcClient.createGrpcClient(isar)); +} + +Future _initializeServices() async { final sharedPreferences = await SharedPreferences.getInstance(); getIt.registerSingleton(ConnectionChecker()); getIt.registerSingleton(MapThemeService()); @@ -78,24 +86,6 @@ Future _initializeGeneral() async { ); } -Future _initializeWeb() async { - getIt.registerSingleton(RESTClient.webCache()); - getIt.registerSingleton( - await CachedCampusClient.createWebCache(), - ); -} - -Future _initializeMobile() async { - final directory = await getTemporaryDirectory(); - Hive.init(directory.path); - Hive.registerAdapter(CacheResponseAdapter()); - getIt.registerSingleton>(await MapLauncher.installedMaps); - getIt.registerSingleton(RESTClient.mobileCache(directory)); - getIt.registerSingleton( - await CachedCampusClient.createMobileCache(directory), - ); -} - Future _initializeHomeWidgets() async { try { HomeWidget.setAppGroupId("group.de.tum.tca-widget"); @@ -183,15 +173,11 @@ class _CampusAppState extends ConsumerState } Locale getDeviceLocale() { - if (kIsWeb) { - return const Locale("en", "DE"); + final deviceLocal = Platform.localeName; + if (deviceLocal.contains("de")) { + return const Locale("de", "DE"); } else { - final deviceLocal = Platform.localeName; - if (deviceLocal.contains("de")) { - return const Locale("de", "DE"); - } else { - return const Locale("en", "DE"); - } + return const Locale("en", "DE"); } } diff --git a/lib/movieComponent/service/movie_service.dart b/lib/movieComponent/service/movie_service.dart index dcaa1762..63098f6e 100644 --- a/lib/movieComponent/service/movie_service.dart +++ b/lib/movieComponent/service/movie_service.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/networking/apis/google/protobuf/timestamp.pb.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/cached_client.dart'; +import 'package:campus_flutter/base/networking/base/grpc_client.dart'; import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; import 'package:campus_flutter/main.dart'; @@ -8,7 +8,7 @@ class MovieService { bool forcedRefresh, ) async { final start = DateTime.now(); - CachedCampusClient restClient = getIt(); + GrpcClient restClient = getIt(); final response = await restClient.listMovies( ListMoviesRequest( oldestDateAt: Timestamp.fromDateTime( diff --git a/lib/movieComponent/views/homeWidget/movie_card_view.dart b/lib/movieComponent/views/homeWidget/movie_card_view.dart index 5a8bf27b..2d57d96a 100644 --- a/lib/movieComponent/views/homeWidget/movie_card_view.dart +++ b/lib/movieComponent/views/homeWidget/movie_card_view.dart @@ -1,5 +1,5 @@ -import 'package:campus_flutter/base/helpers/string_parser.dart'; -import 'package:campus_flutter/base/helpers/url_launcher.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; import 'package:campus_flutter/movieComponent/viewModel/movies_viewmodel.dart'; diff --git a/lib/movieComponent/views/homeWidget/movies_widget_view.dart b/lib/movieComponent/views/homeWidget/movies_widget_view.dart index e5858c79..e6a19423 100644 --- a/lib/movieComponent/views/homeWidget/movies_widget_view.dart +++ b/lib/movieComponent/views/homeWidget/movies_widget_view.dart @@ -1,6 +1,6 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/horizontal_slider.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/horizontal_slider.dart'; import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; diff --git a/lib/navigaTumComponent/services/navigatum_search_service.dart b/lib/navigaTumComponent/services/navigatum_search_service.dart index c3073f44..0f2ba5dc 100644 --- a/lib/navigaTumComponent/services/navigatum_search_service.dart +++ b/lib/navigaTumComponent/services/navigatum_search_service.dart @@ -9,7 +9,7 @@ class NavigaTumSearchService { String query, bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.get( NavigaTumApi( diff --git a/lib/navigaTumComponent/services/navigatum_service.dart b/lib/navigaTumComponent/services/navigatum_service.dart index 84eece58..9a1ce0fa 100644 --- a/lib/navigaTumComponent/services/navigatum_service.dart +++ b/lib/navigaTumComponent/services/navigatum_service.dart @@ -11,7 +11,7 @@ class NavigaTumService { bool forcedRefresh, String query, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.get( NavigaTumApi( @@ -30,7 +30,7 @@ class NavigaTumService { BuildContext context, ) async { final response = - await getIt().get( + await getIt().get( NavigaTumApi( navigaTumApiService: NavigaTumApiServiceDetails( id: id, diff --git a/lib/navigaTumComponent/views/navigatum_room_maps_view.dart b/lib/navigaTumComponent/views/navigatum_room_maps_view.dart index 88d0b1cb..e052c090 100644 --- a/lib/navigaTumComponent/views/navigatum_room_maps_view.dart +++ b/lib/navigaTumComponent/views/navigatum_room_maps_view.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; -import 'package:campus_flutter/base/helpers/horizontal_slider.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; +import 'package:campus_flutter/base/util/horizontal_slider.dart'; import 'package:campus_flutter/base/networking/apis/navigaTumApi/navigatum_api.dart'; import 'package:campus_flutter/base/networking/apis/navigaTumApi/navigatum_api_serivce.dart'; import 'package:campus_flutter/base/routing/routes.dart'; diff --git a/lib/navigaTumComponent/views/navigatum_room_view.dart b/lib/navigaTumComponent/views/navigatum_room_view.dart index bf1db316..1c6c5232 100644 --- a/lib/navigaTumComponent/views/navigatum_room_view.dart +++ b/lib/navigaTumComponent/views/navigatum_room_view.dart @@ -1,6 +1,6 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/navigaTumComponent/model/navigatum_navigation_details.dart'; import 'package:campus_flutter/navigaTumComponent/viewModels/navigatum_details_viewmodel.dart'; diff --git a/lib/navigation_service.dart b/lib/navigation_service.dart index 0c689079..e7bbf99a 100644 --- a/lib/navigation_service.dart +++ b/lib/navigation_service.dart @@ -12,7 +12,6 @@ import 'package:campus_flutter/placesComponent/views/places_screen.dart'; import 'package:campus_flutter/searchComponent/viewModels/global_search_viewmodel.dart'; import 'package:campus_flutter/studentCardComponent/views/student_card_view.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -22,12 +21,8 @@ class NavigationService { double? _leadingWidth; NavigationService() { - if (kIsWeb) { - _leadingWidth = 80; - } else { - if (Platform.isIOS) { - _navigationBarHeight = 49; - } + if (Platform.isIOS) { + _navigationBarHeight = 49; } } @@ -114,14 +109,11 @@ class NavigationService { onPressed: () => WidgetScreen.showHomeSheet(context), icon: const Icon(Icons.edit), ), - // TODO: (Jakob) re-enable digital student card - /* - if (!kIsWeb && MediaQuery.sizeOf(context).width < 600) + if (MediaQuery.sizeOf(context).width < 600) IconButton( - onPressed: () => _openStudentCardSheet(context), + onPressed: () => openStudentCardSheet(context), icon: const Icon(Icons.credit_card), ), - */ IconButton( onPressed: () => context.push(menuSettings), icon: const Icon(Icons.menu), @@ -158,10 +150,11 @@ class NavigationService { ), ]; - void _openStudentCardSheet(BuildContext context) { + static void openStudentCardSheet(BuildContext context) { showModalBottomSheet( isScrollControlled: true, useSafeArea: true, + useRootNavigator: true, showDragHandle: true, context: context, builder: (BuildContext context) { diff --git a/lib/newsComponent/service/news_service.dart b/lib/newsComponent/service/news_service.dart index 08afa05e..718e5e99 100644 --- a/lib/newsComponent/service/news_service.dart +++ b/lib/newsComponent/service/news_service.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/networking/apis/google/protobuf/timestamp.pb.dart'; -import 'package:campus_flutter/base/networking/apis/tumdev/cached_client.dart'; +import 'package:campus_flutter/base/networking/base/grpc_client.dart'; import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; import 'package:campus_flutter/main.dart'; @@ -8,7 +8,7 @@ class NewsService { bool forcedRefresh, ) async { final start = DateTime.now(); - CachedCampusClient restClient = getIt(); + GrpcClient restClient = getIt(); final news = await restClient.listNews( ListNewsRequest( oldestDateAt: Timestamp.fromDateTime( @@ -22,7 +22,7 @@ class NewsService { static Future<(DateTime?, List)> fetchNews(bool forcedRefresh) async { final start = DateTime.now(); - CachedCampusClient restClient = getIt(); + GrpcClient restClient = getIt(); final news = await restClient.listNews(ListNewsRequest()); return (start, news.news); } diff --git a/lib/newsComponent/views/homeWidget/news_widget_view.dart b/lib/newsComponent/views/homeWidget/news_widget_view.dart index 253c506e..ecd43fb7 100644 --- a/lib/newsComponent/views/homeWidget/news_widget_view.dart +++ b/lib/newsComponent/views/homeWidget/news_widget_view.dart @@ -1,6 +1,6 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/horizontal_slider.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/horizontal_slider.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/newsComponent/viewModel/news_viewmodel.dart'; diff --git a/lib/newsComponent/views/news_card_view.dart b/lib/newsComponent/views/news_card_view.dart index 3795060d..cbbc0ae8 100644 --- a/lib/newsComponent/views/news_card_view.dart +++ b/lib/newsComponent/views/news_card_view.dart @@ -1,5 +1,5 @@ import 'package:cached_network_image/cached_network_image.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/base/networking/apis/tumdev/campus_backend.pbgrpc.dart'; import 'package:campus_flutter/base/extensions/context.dart'; import 'package:campus_flutter/base/routing/routes.dart'; diff --git a/lib/onboardingComponent/services/onboarding_service.dart b/lib/onboardingComponent/services/onboarding_service.dart index 97a11b06..5cb99d27 100644 --- a/lib/onboardingComponent/services/onboarding_service.dart +++ b/lib/onboardingComponent/services/onboarding_service.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:campus_flutter/base/networking/apis/tumOnlineApi/tum_online_api.dart'; import 'package:campus_flutter/base/networking/apis/tumOnlineApi/tum_online_api_exception.dart'; import 'package:campus_flutter/base/networking/apis/tumOnlineApi/tum_online_api_service.dart'; @@ -5,7 +7,6 @@ import 'package:campus_flutter/base/networking/base/rest_client.dart'; import 'package:campus_flutter/onboardingComponent/model/confirm.dart'; import 'package:campus_flutter/onboardingComponent/model/token.dart'; import 'package:campus_flutter/main.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:shared_preferences/shared_preferences.dart'; class OnboardingService { @@ -28,13 +29,13 @@ class OnboardingService { } static Future requestNewToken(bool forcedRefresh, String name) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi( TumOnlineServiceTokenRequest( tumId: name, - deviceName: "TCA - ${kIsWeb ? "Web App" : "Mobile"}", + deviceName: "TCA - ${Platform.isIOS ? "iOS" : "Android"}", ), ), Token.fromJson, @@ -45,7 +46,7 @@ class OnboardingService { } static Future confirmToken(bool forcedRefresh) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServiceTokenConfirmation()), diff --git a/lib/onboardingComponent/viewModels/onboarding_viewmodel.dart b/lib/onboardingComponent/viewModels/onboarding_viewmodel.dart index 215aff56..768acb54 100644 --- a/lib/onboardingComponent/viewModels/onboarding_viewmodel.dart +++ b/lib/onboardingComponent/viewModels/onboarding_viewmodel.dart @@ -143,7 +143,7 @@ class OnboardingViewModel { ref.invalidate(profileViewModel); ref.invalidate(personDetailsViewModel); ref.invalidate(studentCardViewModel); - await getIt().clearCache(); + await getIt().clearCache(); await _storage.delete(key: "token"); await HomeWidget.saveWidgetData("calendar", null); await HomeWidget.saveWidgetData("calendar_save", null); diff --git a/lib/onboardingComponent/views/confirm_view.dart b/lib/onboardingComponent/views/confirm_view.dart index 864dabd7..3d3f8385 100644 --- a/lib/onboardingComponent/views/confirm_view.dart +++ b/lib/onboardingComponent/views/confirm_view.dart @@ -1,8 +1,8 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; -import 'package:campus_flutter/base/helpers/url_launcher.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:campus_flutter/base/networking/apis/tumOnlineApi/tum_online_api_exception.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/routing/routes.dart'; diff --git a/lib/onboardingComponent/views/login_view.dart b/lib/onboardingComponent/views/login_view.dart index 80b86316..5d4cdcf7 100644 --- a/lib/onboardingComponent/views/login_view.dart +++ b/lib/onboardingComponent/views/login_view.dart @@ -2,7 +2,6 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/onboardingComponent/viewModels/onboarding_viewmodel.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -31,7 +30,7 @@ class _LoginViewState extends ConsumerState { MediaQuery.platformBrightnessOf(context) == Brightness.dark ? Theme.of(context).canvasColor : Colors.white, - resizeToAvoidBottomInset: orientation != Orientation.portrait && !kIsWeb, + resizeToAvoidBottomInset: orientation != Orientation.portrait, body: SafeArea( maintainBottomViewPadding: true, child: OrientationBuilder( diff --git a/lib/onboardingComponent/views/permission_check_view.dart b/lib/onboardingComponent/views/permission_check_view.dart index 246c8a67..c590e66c 100644 --- a/lib/onboardingComponent/views/permission_check_view.dart +++ b/lib/onboardingComponent/views/permission_check_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/calendarComponent/services/calendar_service.dart'; import 'package:campus_flutter/gradeComponent/services/grade_service.dart'; diff --git a/lib/personDetailedComponent/services/person_details_service.dart b/lib/personDetailedComponent/services/person_details_service.dart index 426f7fc1..573be26a 100644 --- a/lib/personDetailedComponent/services/person_details_service.dart +++ b/lib/personDetailedComponent/services/person_details_service.dart @@ -10,7 +10,7 @@ class PersonDetailsService { bool forcedRefresh, String identNumber, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.getWithException( TumOnlineApi(TumOnlineServicePersonDetails(identNumber: identNumber)), diff --git a/lib/personDetailedComponent/views/person_details_view.dart b/lib/personDetailedComponent/views/person_details_view.dart index 1e1dd20a..0c59c582 100644 --- a/lib/personDetailedComponent/views/person_details_view.dart +++ b/lib/personDetailedComponent/views/person_details_view.dart @@ -1,8 +1,8 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/extensions/base_64_decode_image_data.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/url_launcher.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; diff --git a/lib/personSearchComponent/services/person_search_service.dart b/lib/personSearchComponent/services/person_search_service.dart index 95c20439..31428432 100644 --- a/lib/personSearchComponent/services/person_search_service.dart +++ b/lib/personSearchComponent/services/person_search_service.dart @@ -10,7 +10,7 @@ class PersonSearchService { String query, bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServicePersonSearch(search: query)), diff --git a/lib/placesComponent/model/cafeterias/cafeteria.dart b/lib/placesComponent/model/cafeterias/cafeteria.dart index 9b2c595d..bcfc4d32 100644 --- a/lib/placesComponent/model/cafeterias/cafeteria.dart +++ b/lib/placesComponent/model/cafeterias/cafeteria.dart @@ -77,7 +77,7 @@ class Cafeteria extends Searchable { if (dateTime.isAtSameDay(today)) { return context.localizations.today; } else { - return DateFormat.EEEE().format(dateTime); + return DateFormat.EEEE(context.localizations.localeName).format(dateTime); } } diff --git a/lib/placesComponent/services/cafeterias_service.dart b/lib/placesComponent/services/cafeterias_service.dart index 8f7cbf9e..cfffcc66 100644 --- a/lib/placesComponent/services/cafeterias_service.dart +++ b/lib/placesComponent/services/cafeterias_service.dart @@ -8,7 +8,7 @@ class CafeteriasService { static Future<(DateTime?, List)> fetchCafeterias( bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.get( EatApi(EatApiServiceCanteens()), Cafeterias.fromJson, diff --git a/lib/placesComponent/services/mealplan_service.dart b/lib/placesComponent/services/mealplan_service.dart index b735371f..2dfdc6a6 100644 --- a/lib/placesComponent/services/mealplan_service.dart +++ b/lib/placesComponent/services/mealplan_service.dart @@ -15,7 +15,7 @@ class MealPlanService { bool forcedRefresh, Cafeteria cafeteria, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final today = DateTime.now(); try { /// attempt to fetch current weeks meal plan diff --git a/lib/placesComponent/services/study_rooms_service.dart b/lib/placesComponent/services/study_rooms_service.dart index 957c5804..71770c5b 100644 --- a/lib/placesComponent/services/study_rooms_service.dart +++ b/lib/placesComponent/services/study_rooms_service.dart @@ -8,7 +8,7 @@ class StudyRoomsService { static Future<(DateTime?, StudyRoomData)> fetchStudyRooms( bool forcedRefresh, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient.get( TumDevAppApi(tumDevAppService: TumDevAppServiceRooms()), StudyRoomData.fromJson, diff --git a/lib/placesComponent/viewModels/study_rooms_viewmodel.dart b/lib/placesComponent/viewModels/study_rooms_viewmodel.dart index d7e04749..53c0afee 100644 --- a/lib/placesComponent/viewModels/study_rooms_viewmodel.dart +++ b/lib/placesComponent/viewModels/study_rooms_viewmodel.dart @@ -52,7 +52,10 @@ class StudyRoomsViewModel { ); } - Future fetchWidgetStudyRooms(bool forcedRefresh) async { + Future fetchWidgetStudyRooms( + bool forcedRefresh, + BuildContext context, + ) async { final preferenceId = getIt().load( UserPreference.studyRoom, ); @@ -69,7 +72,7 @@ class StudyRoomsViewModel { widgetStudyRoom.add(selectedStudyRoom); } else { LocationService.getLastKnown().then( - (position) => _getClosestStudyRoomGroup(position), + (position) => _getClosestStudyRoomGroup(position, context), onError: (error) => widgetStudyRoom.addError(error), ); } @@ -78,10 +81,9 @@ class StudyRoomsViewModel { ); } - _getClosestStudyRoomGroup(Position? position) { + _getClosestStudyRoomGroup(Position? position, BuildContext context) { if (studyRoomData?.groups == null) { - // TODO: localize - widgetStudyRoom.addError("Could not get closest study rooms!"); + widgetStudyRoom.addError(context.localizations.noClosestStudyRoom); return; } @@ -91,28 +93,28 @@ class StudyRoomsViewModel { ) ?? studyRoomData?.groups?.firstOrNull; if (defaultStudyRoom == null) { - // TODO: localize - widgetStudyRoom.addError("Could not get closest study rooms!"); + widgetStudyRoom.addError(context.localizations.noClosestStudyRoom); } else { widgetStudyRoom.add(defaultStudyRoom); } } final group = studyRoomData?.groups?.reduce((currentGroup, nextGroup) { - final distanceCurrent = currentGroup.coordinate != null - ? Geolocator.distanceBetween( - currentGroup.coordinate!.latitude, - currentGroup.coordinate!.longitude, - position!.latitude, - position.longitude, - ) - : 0.0; + final distanceCurrent = + (currentGroup.coordinate != null && position != null) + ? Geolocator.distanceBetween( + currentGroup.coordinate!.latitude, + currentGroup.coordinate!.longitude, + position.latitude, + position.longitude, + ) + : 0.0; - final distanceNext = nextGroup.coordinate != null + final distanceNext = (nextGroup.coordinate != null && position != null) ? Geolocator.distanceBetween( nextGroup.coordinate!.latitude, nextGroup.coordinate!.longitude, - position!.latitude, + position.latitude, position.longitude, ) : 0.0; @@ -214,7 +216,7 @@ class StudyRoomsViewModel { : null, onTap: () { getIt().reset(UserPreference.studyRoom); - fetchWidgetStudyRooms(false); + fetchWidgetStudyRooms(false, context); context.pop(); }, ), diff --git a/lib/placesComponent/views/cafeterias/cafeteria_row_view.dart b/lib/placesComponent/views/cafeterias/cafeteria_row_view.dart index e714eadc..441543d1 100644 --- a/lib/placesComponent/views/cafeterias/cafeteria_row_view.dart +++ b/lib/placesComponent/views/cafeterias/cafeteria_row_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; import 'package:campus_flutter/base/routing/routes.dart' as routes; import 'package:campus_flutter/placesComponent/model/cafeterias/cafeteria.dart'; import 'package:flutter/material.dart'; diff --git a/lib/placesComponent/views/cafeterias/cafeteria_view.dart b/lib/placesComponent/views/cafeterias/cafeteria_view.dart index 1cdb13de..8202f035 100644 --- a/lib/placesComponent/views/cafeterias/cafeteria_view.dart +++ b/lib/placesComponent/views/cafeterias/cafeteria_view.dart @@ -1,9 +1,9 @@ import 'package:campus_flutter/base/classes/location.dart' as location; import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/directions_launcher.dart'; -import 'package:campus_flutter/base/helpers/info_row.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/directions_launcher.dart'; +import 'package:campus_flutter/base/util/info_row.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/placesComponent/model/cafeterias/cafeteria.dart'; import 'package:campus_flutter/placesComponent/model/cafeterias/opening_hours.dart'; @@ -41,13 +41,13 @@ class CafeteriaScaffold extends ConsumerWidget { ), ), IconButton( - onPressed: () => launchDirections( + onPressed: () => showDirectionsDialog( + cafeteria.name, location.Location( latitude: cafeteria.location.latitude, longitude: cafeteria.location.longitude, ), - cafeteria.name, - ref, + context, ), icon: Icon( Icons.directions, @@ -167,8 +167,6 @@ class _CafeteriaViewState extends ConsumerState { children: [ if (openingHours.$2 != null && openingHours.$1) _openingTimes(openingHours, context), - //..._mapAndDirections(), - //const PaddedDivider(),*/ _pickerAndSlider(false), ], ); @@ -177,7 +175,6 @@ class _CafeteriaViewState extends ConsumerState { ); } - // TODO: optimize flow Widget _openingTimes( (bool, OpeningHour?) openingHours, BuildContext context, diff --git a/lib/placesComponent/views/cafeterias/cafeterias_view.dart b/lib/placesComponent/views/cafeterias/cafeterias_view.dart index 9bea105e..c1f3de20 100644 --- a/lib/placesComponent/views/cafeterias/cafeterias_view.dart +++ b/lib/placesComponent/views/cafeterias/cafeterias_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; diff --git a/lib/placesComponent/views/cafeterias/dish_card_view.dart b/lib/placesComponent/views/cafeterias/dish_card_view.dart index e5a08065..73b043fd 100644 --- a/lib/placesComponent/views/cafeterias/dish_card_view.dart +++ b/lib/placesComponent/views/cafeterias/dish_card_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; import 'package:campus_flutter/placesComponent/model/cafeterias/dish.dart'; import 'package:campus_flutter/placesComponent/viewModels/cafeterias_viewmodel.dart'; import 'package:flutter/material.dart'; diff --git a/lib/placesComponent/views/cafeterias/dish_slider_view.dart b/lib/placesComponent/views/cafeterias/dish_slider_view.dart index acc591bf..b7af057c 100644 --- a/lib/placesComponent/views/cafeterias/dish_slider_view.dart +++ b/lib/placesComponent/views/cafeterias/dish_slider_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/horizontal_slider.dart'; +import 'package:campus_flutter/base/util/horizontal_slider.dart'; import 'package:campus_flutter/placesComponent/model/cafeterias/dish.dart'; import 'package:campus_flutter/placesComponent/views/cafeterias/dish_card_view.dart'; import 'package:flutter/material.dart'; diff --git a/lib/placesComponent/views/campuses/campus_map_view.dart b/lib/placesComponent/views/campuses/campus_map_view.dart index 1c0b4c0c..a096af36 100644 --- a/lib/placesComponent/views/campuses/campus_map_view.dart +++ b/lib/placesComponent/views/campuses/campus_map_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/campus.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/placesComponent/viewModels/places_viewmodel.dart'; import 'package:campus_flutter/placesComponent/views/map_widget.dart'; import 'package:flutter/material.dart'; diff --git a/lib/placesComponent/views/campuses/campus_most_searched_view.dart b/lib/placesComponent/views/campuses/campus_most_searched_view.dart index 1b7704f2..d6afc37a 100644 --- a/lib/placesComponent/views/campuses/campus_most_searched_view.dart +++ b/lib/placesComponent/views/campuses/campus_most_searched_view.dart @@ -1,6 +1,6 @@ import 'package:campus_flutter/base/enums/campus.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/navigaTumComponent/viewModels/navigatum_campus_viewmodel.dart'; diff --git a/lib/placesComponent/views/campuses/campus_scaffold.dart b/lib/placesComponent/views/campuses/campus_scaffold.dart index 0c26b4eb..f0862d61 100644 --- a/lib/placesComponent/views/campuses/campus_scaffold.dart +++ b/lib/placesComponent/views/campuses/campus_scaffold.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/campus.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/placesComponent/views/campuses/campus_view.dart'; import 'package:campus_flutter/base/extensions/context.dart'; diff --git a/lib/placesComponent/views/campuses/campus_view.dart b/lib/placesComponent/views/campuses/campus_view.dart index 486994e1..a716ead4 100644 --- a/lib/placesComponent/views/campuses/campus_view.dart +++ b/lib/placesComponent/views/campuses/campus_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/campus.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/navigaTumComponent/viewModels/navigatum_campus_viewmodel.dart'; diff --git a/lib/placesComponent/views/directions_button.dart b/lib/placesComponent/views/directions_button.dart index 63ca7085..e3bd241e 100644 --- a/lib/placesComponent/views/directions_button.dart +++ b/lib/placesComponent/views/directions_button.dart @@ -1,6 +1,6 @@ import 'package:campus_flutter/base/classes/location.dart'; -import 'package:campus_flutter/base/helpers/directions_launcher.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/directions_launcher.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/base/extensions/context.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -35,13 +35,13 @@ class DirectionsButton extends ConsumerWidget { height: 50, width: double.infinity, child: ElevatedButton( - onPressed: () => launchDirections( + onPressed: () => showDirectionsDialog( + name ?? "Destination", Location( latitude: location?.latitude ?? latitude!, longitude: location?.longitude ?? longitude!, ), - name ?? "Destination", - ref, + context, ), child: IconText( iconData: Icons.directions, diff --git a/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart b/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart index 1a90aa06..bcd0ffa9 100644 --- a/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart +++ b/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/preference_selection_view.dart'; diff --git a/lib/placesComponent/views/homeWidget/study_room_widget_view.dart b/lib/placesComponent/views/homeWidget/study_room_widget_view.dart index d8af4c42..f86ec77c 100644 --- a/lib/placesComponent/views/homeWidget/study_room_widget_view.dart +++ b/lib/placesComponent/views/homeWidget/study_room_widget_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/homeComponent/split_view_viewmodel.dart'; @@ -46,11 +46,11 @@ class StudyRoomWidgetView extends ConsumerStatefulWidget { class _StudyRoomWidgetViewState extends ConsumerState { @override - void initState() { + void didChangeDependencies() { if (widget.closestStudyRoom) { - ref.read(studyRoomsViewModel).fetchWidgetStudyRooms(false); + ref.read(studyRoomsViewModel).fetchWidgetStudyRooms(false, context); } - super.initState(); + super.didChangeDependencies(); } @override diff --git a/lib/placesComponent/views/places_screen.dart b/lib/placesComponent/views/places_screen.dart index 185ae9fc..d0d5ce46 100644 --- a/lib/placesComponent/views/places_screen.dart +++ b/lib/placesComponent/views/places_screen.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/placesComponent/viewModels/cafeterias_viewmodel.dart'; import 'package:campus_flutter/placesComponent/viewModels/places_viewmodel.dart'; diff --git a/lib/placesComponent/views/places_view.dart b/lib/placesComponent/views/places_view.dart index 6253b03b..e9a69560 100644 --- a/lib/placesComponent/views/places_view.dart +++ b/lib/placesComponent/views/places_view.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/placesComponent/viewModels/places_viewmodel.dart'; import 'package:campus_flutter/placesComponent/views/campuses/campus_card_view.dart'; diff --git a/lib/placesComponent/views/studyGroups/study_room_group_scaffold.dart b/lib/placesComponent/views/studyGroups/study_room_group_scaffold.dart index 22ae62f7..48d97d90 100644 --- a/lib/placesComponent/views/studyGroups/study_room_group_scaffold.dart +++ b/lib/placesComponent/views/studyGroups/study_room_group_scaffold.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/placesComponent/model/studyRooms/study_room_group.dart'; import 'package:campus_flutter/placesComponent/views/studyGroups/study_room_group_view.dart'; import 'package:flutter/material.dart'; diff --git a/lib/placesComponent/views/studyGroups/study_room_group_view.dart b/lib/placesComponent/views/studyGroups/study_room_group_view.dart index 350012be..ee531df8 100644 --- a/lib/placesComponent/views/studyGroups/study_room_group_view.dart +++ b/lib/placesComponent/views/studyGroups/study_room_group_view.dart @@ -1,6 +1,6 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/last_updated_text.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/last_updated_text.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; diff --git a/lib/placesComponent/views/studyGroups/study_rooms_view.dart b/lib/placesComponent/views/studyGroups/study_rooms_view.dart index 5b6fd65c..7b6e192b 100644 --- a/lib/placesComponent/views/studyGroups/study_rooms_view.dart +++ b/lib/placesComponent/views/studyGroups/study_rooms_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; diff --git a/lib/profileComponent/model/tuition.dart b/lib/profileComponent/model/tuition.dart index 05e72d24..3d182dbd 100644 --- a/lib/profileComponent/model/tuition.dart +++ b/lib/profileComponent/model/tuition.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:json_annotation/json_annotation.dart'; part 'tuition.g.dart'; diff --git a/lib/profileComponent/services/profile_service.dart b/lib/profileComponent/services/profile_service.dart index 1558d4a0..d1525f7f 100644 --- a/lib/profileComponent/services/profile_service.dart +++ b/lib/profileComponent/services/profile_service.dart @@ -8,7 +8,7 @@ import 'package:campus_flutter/profileComponent/model/tuition.dart'; class ProfileService { static Future<(DateTime?, Profile)> fetchProfile(bool forcedRefresh) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServiceIdentify()), @@ -25,7 +25,7 @@ class ProfileService { String personGroup, String id, ) async { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServiceTuitionStatus()), diff --git a/lib/searchComponent/viewModels/global_search_viewmodel.dart b/lib/searchComponent/viewModels/global_search_viewmodel.dart index cecfecfd..973580e5 100644 --- a/lib/searchComponent/viewModels/global_search_viewmodel.dart +++ b/lib/searchComponent/viewModels/global_search_viewmodel.dart @@ -57,6 +57,10 @@ class GlobalSearchViewModel { selectedCategories.add(categories); } + void selectSingleCategory(SearchCategory searchCategory) { + selectedCategories.add([searchCategory]); + } + void clear() { searchString = ""; result.add(null); diff --git a/lib/searchComponent/views/appWideSearch/resultViews/calendar_search_result_view.dart b/lib/searchComponent/views/appWideSearch/resultViews/calendar_search_result_view.dart index 12a227ff..3b7ed868 100644 --- a/lib/searchComponent/views/appWideSearch/resultViews/calendar_search_result_view.dart +++ b/lib/searchComponent/views/appWideSearch/resultViews/calendar_search_result_view.dart @@ -1,5 +1,5 @@ -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; import 'package:campus_flutter/lectureComponent/views/lecture_details_view.dart'; import 'package:campus_flutter/base/enums/search_category.dart'; diff --git a/lib/searchComponent/views/appWideSearch/resultViews/movie_search_result_view.dart b/lib/searchComponent/views/appWideSearch/resultViews/movie_search_result_view.dart index d0c9cd9d..64de822e 100644 --- a/lib/searchComponent/views/appWideSearch/resultViews/movie_search_result_view.dart +++ b/lib/searchComponent/views/appWideSearch/resultViews/movie_search_result_view.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; -import 'package:campus_flutter/base/helpers/url_launcher.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:campus_flutter/base/enums/search_category.dart'; import 'package:campus_flutter/searchComponent/viewModels/searchableViewModels/movie_search_viewmodel.dart'; import 'package:campus_flutter/searchComponent/views/appWideSearch/search_result_card_view.dart'; diff --git a/lib/searchComponent/views/appWideSearch/resultViews/news_search_result_view.dart b/lib/searchComponent/views/appWideSearch/resultViews/news_search_result_view.dart index 9cc41f1c..4f66e160 100644 --- a/lib/searchComponent/views/appWideSearch/resultViews/news_search_result_view.dart +++ b/lib/searchComponent/views/appWideSearch/resultViews/news_search_result_view.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:campus_flutter/base/enums/search_category.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/searchComponent/viewModels/searchableViewModels/news_search_viewmodel.dart'; import 'package:campus_flutter/searchComponent/views/appWideSearch/search_result_card_view.dart'; diff --git a/lib/searchComponent/views/appWideSearch/search_category_picker_view.dart b/lib/searchComponent/views/appWideSearch/search_category_picker_view.dart index a6164db9..00ac50a7 100644 --- a/lib/searchComponent/views/appWideSearch/search_category_picker_view.dart +++ b/lib/searchComponent/views/appWideSearch/search_category_picker_view.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/enums/credentials.dart'; -import 'package:campus_flutter/base/helpers/horizontal_slider.dart'; +import 'package:campus_flutter/base/util/horizontal_slider.dart'; import 'package:campus_flutter/onboardingComponent/viewModels/onboarding_viewmodel.dart'; import 'package:campus_flutter/base/enums/search_category.dart'; import 'package:campus_flutter/searchComponent/viewModels/global_search_viewmodel.dart'; @@ -19,20 +19,26 @@ class SearchCategoryPickerView extends ConsumerWidget { child: HorizontalSlider.height( data: _getData(snapshot.data ?? [], ref), height: 40, - child: (searchCategory) => FilterChip( - label: Text( - SearchCategoryExtension.localizedEnumTitle( - searchCategory, - context, - ), - ), - onSelected: (selected) { - ref.read(searchViewModel).updateCategory(searchCategory); + child: (searchCategory) => InkWell( + onLongPress: () { + ref.read(searchViewModel).selectSingleCategory(searchCategory); ref.read(searchViewModel).triggerSearchAfterUpdate(null); }, - selected: (snapshot.data ?? []).isNotEmpty - ? snapshot.data?.contains(searchCategory) ?? false - : true, + child: FilterChip( + label: Text( + SearchCategoryExtension.localizedEnumTitle( + searchCategory, + context, + ), + ), + onSelected: (selected) { + ref.read(searchViewModel).updateCategory(searchCategory); + ref.read(searchViewModel).triggerSearchAfterUpdate(null); + }, + selected: (snapshot.data ?? []).isNotEmpty + ? snapshot.data?.contains(searchCategory) ?? false + : true, + ), ), ), ); diff --git a/lib/searchComponent/views/appWideSearch/search_result_card_view.dart b/lib/searchComponent/views/appWideSearch/search_result_card_view.dart index 527fa27f..ab720df5 100644 --- a/lib/searchComponent/views/appWideSearch/search_result_card_view.dart +++ b/lib/searchComponent/views/appWideSearch/search_result_card_view.dart @@ -2,8 +2,8 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/enums/search_category.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/searchComponent/protocols/search_viewmodel.dart'; import 'package:campus_flutter/searchComponent/protocols/searchable.dart'; diff --git a/lib/searchComponent/views/appWideSearch/search_result_details_view.dart b/lib/searchComponent/views/appWideSearch/search_result_details_view.dart index a4020641..903f239b 100644 --- a/lib/searchComponent/views/appWideSearch/search_result_details_view.dart +++ b/lib/searchComponent/views/appWideSearch/search_result_details_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/search_category.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/padded_divider.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/padded_divider.dart'; import 'package:campus_flutter/searchComponent/protocols/searchable.dart'; import 'package:flutter/material.dart'; diff --git a/lib/searchComponent/views/appWideSearch/search_scaffold.dart b/lib/searchComponent/views/appWideSearch/search_scaffold.dart index d69033ee..7621de7c 100644 --- a/lib/searchComponent/views/appWideSearch/search_scaffold.dart +++ b/lib/searchComponent/views/appWideSearch/search_scaffold.dart @@ -1,4 +1,4 @@ -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/searchComponent/viewModels/global_search_viewmodel.dart'; import 'package:campus_flutter/searchComponent/views/appWideSearch/search_view.dart'; import 'package:campus_flutter/base/extensions/context.dart'; diff --git a/lib/searchComponent/views/personRoomSearch/search_view.dart b/lib/searchComponent/views/personRoomSearch/search_view.dart index 98488a7c..e2f22eb3 100644 --- a/lib/searchComponent/views/personRoomSearch/search_view.dart +++ b/lib/searchComponent/views/personRoomSearch/search_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/navigaTumComponent/model/navigatum_navigation_entity.dart'; diff --git a/lib/settingsComponent/service/user_preferences_service.dart b/lib/settingsComponent/service/user_preferences_service.dart index 4a134db8..6543e3d7 100644 --- a/lib/settingsComponent/service/user_preferences_service.dart +++ b/lib/settingsComponent/service/user_preferences_service.dart @@ -20,7 +20,6 @@ class UserPreferencesService { } } - // TODO: handle success/failure of saving void save(UserPreference userPreference, Object value) { if (userPreference.type == String && value is String) { sharedPreferences.setString(userPreference.name, value); diff --git a/lib/settingsComponent/viewModels/user_preferences_viewmodel.dart b/lib/settingsComponent/viewModels/user_preferences_viewmodel.dart index a55025f4..b78bd906 100644 --- a/lib/settingsComponent/viewModels/user_preferences_viewmodel.dart +++ b/lib/settingsComponent/viewModels/user_preferences_viewmodel.dart @@ -1,15 +1,11 @@ import 'package:campus_flutter/base/enums/appearance.dart'; import 'package:campus_flutter/base/enums/user_preference.dart'; -import 'package:campus_flutter/base/helpers/icon_text.dart'; +import 'package:campus_flutter/base/util/icon_text.dart'; import 'package:campus_flutter/main.dart'; import 'package:campus_flutter/settingsComponent/service/user_preferences_service.dart'; -import 'package:campus_flutter/settingsComponent/views/default_maps_picker_view.dart'; import 'package:campus_flutter/settingsComponent/views/settings_view.dart'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:map_launcher/map_launcher.dart'; final userPreferencesViewModel = Provider( (ref) => UserPreferencesViewModel(ref), @@ -37,17 +33,6 @@ class UserPreferencesViewModel { ref.read(hideFailedGrades.notifier).state = value as bool; case UserPreference.locale: ref.read(customLocale.notifier).state = Locale(value as String); - case UserPreference.defaultMapsApplication: - final installedMaps = getIt>(); - final matchingMaps = installedMaps.firstWhereOrNull( - (e) => e.mapType.name == value as String, - ); - if (matchingMaps != null) { - ref.read(selectedMapsApp.notifier).state = matchingMaps; - } else { - ref.read(selectedMapsApp.notifier).state = - installedMaps.firstOrNull; - } default: break; } @@ -68,9 +53,6 @@ class UserPreferencesViewModel { ref.read(hideFailedGrades.notifier).state = value as bool; case UserPreference.locale: ref.read(customLocale.notifier).state = value as Locale?; - case UserPreference.defaultMapsApplication: - ref.read(selectedMapsApp.notifier).state = value as AvailableMap?; - value = value?.mapName; default: break; } @@ -85,30 +67,6 @@ class UserPreferencesViewModel { } } - static List> getInstalledMapTypes( - BuildContext context, - ) { - return getIt.get>().mapIndexed((index, e) { - return PopupMenuItem( - value: e, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(5), - child: SvgPicture.asset( - e.icon, - height: 30, - width: 30, - ), - ), - Text(e.mapName), - ], - ), - ); - }).toList(); - } - static List> getAppearanceEntries( BuildContext context, ) { diff --git a/lib/settingsComponent/views/appearance_settings_view.dart b/lib/settingsComponent/views/appearance_settings_view.dart index 3c39db62..0a3c1bcb 100644 --- a/lib/settingsComponent/views/appearance_settings_view.dart +++ b/lib/settingsComponent/views/appearance_settings_view.dart @@ -8,12 +8,9 @@ import 'package:campus_flutter/gradeComponent/viewModels/grade_viewmodel.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/main.dart'; import 'package:campus_flutter/settingsComponent/viewModels/user_preferences_viewmodel.dart'; -import 'package:campus_flutter/settingsComponent/views/default_maps_picker_view.dart'; import 'package:campus_flutter/settingsComponent/views/settings_view.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:map_launcher/map_launcher.dart'; class AppearanceSettingsView extends ConsumerWidget { const AppearanceSettingsView({super.key}); @@ -26,10 +23,8 @@ class AppearanceSettingsView extends ConsumerWidget { child: SeparatedList.widgets( widgets: [ _appearanceSelection(context, ref), - if (!kIsWeb && Platform.isIOS) _useWebView(context, ref), + if (Platform.isIOS) _useWebView(context, ref), _hideFailedGrades(context, ref), - if (!kIsWeb && getIt.get>().isNotEmpty) - const DefaultMapsPickerView(), ], ), ), diff --git a/lib/settingsComponent/views/contact_view.dart b/lib/settingsComponent/views/contact_view.dart index 898e93eb..23ee4f9c 100644 --- a/lib/settingsComponent/views/contact_view.dart +++ b/lib/settingsComponent/views/contact_view.dart @@ -1,11 +1,10 @@ import 'dart:io'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/hyperlink_text.dart'; +import 'package:campus_flutter/base/util/hyperlink_text.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/base/views/seperated_list.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -20,12 +19,11 @@ class ContactView extends ConsumerWidget { child: Card( child: SeparatedList.widgets( widgets: [ - if (!kIsWeb) - HyperLinkListTile( - dense: true, - link: _betaTester(), - label: context.localizations.becomeABetaTester, - ), + HyperLinkListTile( + dense: true, + link: _betaTester(), + label: context.localizations.becomeABetaTester, + ), HyperLinkListTile( dense: true, link: "https://github.com/TUM-Dev", diff --git a/lib/settingsComponent/views/default_maps_picker_view.dart b/lib/settingsComponent/views/default_maps_picker_view.dart deleted file mode 100644 index a4e58d83..00000000 --- a/lib/settingsComponent/views/default_maps_picker_view.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:campus_flutter/base/enums/user_preference.dart'; -import 'package:campus_flutter/settingsComponent/viewModels/user_preferences_viewmodel.dart'; -import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:map_launcher/map_launcher.dart'; - -final selectedMapsApp = StateProvider((ref) => null); - -class DefaultMapsPickerView extends ConsumerStatefulWidget { - const DefaultMapsPickerView({super.key}); - - @override - ConsumerState createState() => - _DefaultMapsPickerViewState(); -} - -class _DefaultMapsPickerViewState extends ConsumerState { - @override - Widget build(BuildContext context) { - return ListTile( - dense: true, - title: Text( - context.localizations.defaultMapsApplication, - style: Theme.of(context).textTheme.bodyMedium, - ), - trailing: PopupMenuButton( - icon: ref.read(selectedMapsApp)?.icon != null - ? ClipRRect( - borderRadius: BorderRadius.circular(5), - child: SvgPicture.asset( - ref.read(selectedMapsApp)!.icon, - height: 30, - width: 30, - ), - ) - : null, - itemBuilder: UserPreferencesViewModel.getInstalledMapTypes, - initialValue: ref.read(selectedMapsApp), - onSelected: (selection) { - ref.read(userPreferencesViewModel).savePreference( - UserPreference.defaultMapsApplication, - selection, - ); - setState(() {}); - }, - ), - ); - } -} diff --git a/lib/settingsComponent/views/settings_scaffold.dart b/lib/settingsComponent/views/settings_scaffold.dart index 1c09e684..51513844 100644 --- a/lib/settingsComponent/views/settings_scaffold.dart +++ b/lib/settingsComponent/views/settings_scaffold.dart @@ -1,5 +1,5 @@ import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/custom_back_button.dart'; +import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/base/routing/routes.dart'; import 'package:campus_flutter/settingsComponent/views/settings_view.dart'; import 'package:flutter/material.dart'; diff --git a/lib/studentCardComponent/services/student_card_service.dart b/lib/studentCardComponent/services/student_card_service.dart index d789a69e..c3b3a7df 100644 --- a/lib/studentCardComponent/services/student_card_service.dart +++ b/lib/studentCardComponent/services/student_card_service.dart @@ -12,7 +12,7 @@ class StudentCardService { bool forcedRefresh, ) async { try { - RESTClient restClient = getIt(); + RestClient restClient = getIt(); final response = await restClient .getWithException( TumOnlineApi(TumOnlineServiceTumCard()), diff --git a/lib/studentCardComponent/views/information_view.dart b/lib/studentCardComponent/views/information_view.dart index 633c0d1d..1efe96a5 100644 --- a/lib/studentCardComponent/views/information_view.dart +++ b/lib/studentCardComponent/views/information_view.dart @@ -1,7 +1,7 @@ import 'package:campus_flutter/base/extensions/base_64_decode_image_data.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; -import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; +import 'package:campus_flutter/base/util/string_parser.dart'; import 'package:campus_flutter/studentCardComponent/model/student_card.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; diff --git a/lib/studentCardComponent/views/snapping_slider.dart b/lib/studentCardComponent/views/snapping_slider.dart index 2404b6e3..a8415f51 100644 --- a/lib/studentCardComponent/views/snapping_slider.dart +++ b/lib/studentCardComponent/views/snapping_slider.dart @@ -1,6 +1,6 @@ import 'package:barcode_widget/barcode_widget.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; import 'package:flutter/material.dart'; class SnappingSlider extends StatefulWidget { diff --git a/lib/studentCardComponent/views/student_card_view.dart b/lib/studentCardComponent/views/student_card_view.dart index 7376dd99..9768d237 100644 --- a/lib/studentCardComponent/views/student_card_view.dart +++ b/lib/studentCardComponent/views/student_card_view.dart @@ -1,9 +1,9 @@ import 'package:campus_flutter/base/enums/error_handling_view_type.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/extensions/context.dart'; -import 'package:campus_flutter/base/helpers/card_with_padding.dart'; -import 'package:campus_flutter/base/helpers/delayed_loading_indicator.dart'; -import 'package:campus_flutter/base/helpers/last_updated_text.dart'; +import 'package:campus_flutter/base/util/card_with_padding.dart'; +import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; +import 'package:campus_flutter/base/util/last_updated_text.dart'; import 'package:campus_flutter/studentCardComponent/viewModel/student_card_viewmodel.dart'; import 'package:campus_flutter/studentCardComponent/views/bar_code_view.dart'; import 'package:campus_flutter/studentCardComponent/views/information_view.dart'; diff --git a/pubspec.lock b/pubspec.lock index b204de15..7dbf5c65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -257,6 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.6" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" device_info_plus: dependency: "direct main" description: @@ -281,22 +289,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.1" - dio_cache_interceptor: - dependency: "direct main" - description: - name: dio_cache_interceptor - sha256: fb7905c0d12075d8786a6b63bffd64ae062d053f682cfaf28d145a2686507308 - url: "https://pub.dev" - source: hosted - version: "3.5.0" - dio_cache_interceptor_hive_store: - dependency: "direct main" - description: - name: dio_cache_interceptor_hive_store - sha256: "449b36541216cb20543228081125ad2995eb9712ec35bd030d85663ea1761895" - url: "https://pub.dev" - source: hosted - version: "3.2.2" fake_async: dependency: transitive description: @@ -497,7 +489,7 @@ packages: source: sdk version: "0.0.0" flutter_web_plugins: - dependency: "direct main" + dependency: transitive description: flutter source: sdk version: "0.0.0" @@ -553,10 +545,10 @@ packages: dependency: transitive description: name: geolocator_windows - sha256: a92fae29779d5c6dc60e8411302f5221ade464968fe80a36d330e80a71f087af + sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.3" get_it: dependency: "direct main" description: @@ -577,10 +569,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "170c46e237d6eb0e6e9f0e8b3f56101e14fb64f787016e42edd74c39cf8b176a" + sha256: "7ecb2f391edbca5473db591b48555a8912dde60edd0fb3013bd6743033b2d3f8" url: "https://pub.dev" source: hosted - version: "13.2.0" + version: "13.2.1" google_identity_services_web: dependency: transitive description: @@ -618,10 +610,10 @@ packages: description: path: "packages/google_maps_flutter/google_maps_flutter_ios" ref: main - resolved-ref: "723aa415ef071f0cc2855434cbdaa871ca6417e6" + resolved-ref: "974b8b4cc01daec459197d5d9eef6eb69fb14b78" url: "https://github.com/jakobkoerber/packages.git" source: git - version: "2.5.0" + version: "2.5.1" google_maps_flutter_platform_interface: dependency: transitive description: @@ -663,14 +655,6 @@ packages: url: "https://github.com/jakobkoerber/grpc-dart.git" source: git version: "3.2.4" - hive: - dependency: "direct main" - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" home_widget: dependency: "direct main" description: @@ -743,6 +727,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + isar: + dependency: "direct main" + description: + name: isar + sha256: "3510ffb73db4bf46935bd8897f7bb96fc41bbdf9ed655275484f95f9123db35d" + url: "https://isar-community.dev" + source: hosted + version: "3.1.4" + isar_flutter_libs: + dependency: "direct main" + description: + name: isar_flutter_libs + sha256: "2d9a2628e946454a2c623e108dbb1cbb28a5b0bc59588c5c1e5ffa307fa7652d" + url: "https://isar-community.dev" + source: hosted + version: "3.1.4" + isar_generator: + dependency: "direct dev" + description: + name: isar_generator + sha256: "558fcc50f99467c5aa2237f00890455725da698ce27bab2ed6847eb1634ef1b8" + url: "https://isar-community.dev" + source: hosted + version: "3.1.4" js: dependency: transitive description: @@ -1360,10 +1368,10 @@ packages: dependency: "direct main" description: name: syncfusion_flutter_calendar - sha256: "21dfed4c2ef3259cd5e7b71c5b2669baf32db90cfd04247363571a1a5546d6fd" + sha256: "4151e01aa1d6f17b2bcb19d709b93f231d40c9ce78db52ea195b7b7233ce0149" url: "https://pub.dev" source: hosted - version: "24.2.5" + version: "24.2.4" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1428,6 +1436,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.9" + time: + dependency: transitive + description: + name: time + sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + url: "https://pub.dev" + source: hosted + version: "2.1.4" timeago: dependency: "direct main" description: @@ -1693,6 +1709,14 @@ packages: url: "https://github.com/jakobkoerber/xml2json.git" source: git version: "6.2.2" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 + url: "https://pub.dev" + source: hosted + version: "1.0.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e8ab7eba..fde2a623 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,57 +7,71 @@ environment: sdk: '>=3.2.3 <4.0.0' dependencies: - cached_network_image: ^3.2.3 - device_info_plus: ^9.1.1 - dio: ^5.1.2 - dio_cache_interceptor: ^3.4.2 - dio_cache_interceptor_hive_store: ^3.2.1 flutter: sdk: flutter - flutter_web_plugins: - sdk: flutter - flutter_native_splash: ^2.2.19 - flutter_secure_storage: ^9.0.0 - geolocator: ^11.0.0 - hive: ^2.2.3 + + # networking + dio: ^5.1.2 + # dio_cache_interceptor: ^3.5.0 + isar: + version: ^3.1.0+1 + hosted: https://isar-community.dev/ + isar_flutter_libs: + version: ^3.1.0+1 + hosted: https://isar-community.dev/ json_annotation: ^4.8.0 json_serializable: ^6.6.1 - package_info_plus: ^5.0.0 - path_provider: ^2.0.15 - rxdart: ^0.27.7 - shimmer: ^3.0.0 - syncfusion_flutter_calendar: "24.2.5" - syncfusion_flutter_charts: ^24.1.41 - url_launcher: ^6.1.10 - video_player: ^2.6.1 + cached_network_image: ^3.2.3 + grpc: ^3.2.4 + protobuf: ^3.0.0 xml: ^6.2.2 xml2json: ^6.1.0 - barcode_widget: ^2.0.4 - lottie: ^3.0.0 - shared_preferences: ^2.1.2 + + # state management + rxdart: ^0.27.7 + go_router: ^13.2.0 flutter_riverpod: ^2.3.6 get_it: ^7.6.0 - timeago: ^3.4.0 - grpc: ^3.2.4 - protobuf: ^3.0.0 - syncfusion_flutter_datepicker: ^24.1.41 - google_maps_flutter: ^2.5.3 - map_launcher: ^3.0.1 + + # user interface + shimmer: ^3.0.0 flutter_staggered_grid_view: ^0.7.0 flutter_svg: ^2.0.9 - uuid: ^4.2.1 - collection: ^1.18.0 - firebase_crashlytics: ^3.4.6 - firebase_core: ^2.24.0 flutter_linkify: ^6.0.0 - permission_handler: ^11.1.0 home_widget: ^0.4.1 auto_size_text: ^3.0.0 - go_router: ^13.2.0 quick_actions: ^1.0.7 - intl: any + flutter_native_splash: ^2.2.19 + syncfusion_flutter_calendar: "24.2.4" + syncfusion_flutter_charts: "24.2.9" + syncfusion_flutter_datepicker: "24.2.9" + google_maps_flutter: ^2.5.3 + video_player: ^2.6.1 + barcode_widget: ^2.0.4 + lottie: ^3.0.0 + timeago: ^3.4.0 + + # helpers + device_info_plus: ^9.1.1 + flutter_secure_storage: ^9.0.0 + geolocator: ^11.0.0 + package_info_plus: ^5.0.0 + path_provider: ^2.0.15 + url_launcher: ^6.1.10 + permission_handler: ^11.1.0 + uuid: ^4.2.1 + collection: ^1.18.0 + shared_preferences: ^2.1.2 + map_launcher: ^3.0.1 + + # firebase + firebase_crashlytics: ^3.4.6 + firebase_core: ^2.24.0 + + # localization flutter_localizations: sdk: flutter + intl: any dependency_overrides: xml2json: @@ -75,11 +89,19 @@ dependency_overrides: ref: main dev_dependencies: + # code generation + build_runner: ^2.3.3 + isar_generator: + version: ^3.1.0+1 + hosted: https://isar-community.dev/ + + # testing flutter_test: sdk: flutter test: ^1.24.3 + + # linting flutter_lints: ^3.0.0 - build_runner: ^2.3.3 lints: ^3.0.0 flutter: diff --git a/web/favicon.png b/web/favicon.png deleted file mode 100644 index c3d8829c9f47882100306aecace10b673bb55330..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1941 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 C?Ox9S diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 0157893e..00000000 --- a/web/index.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - campus_flutter - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web/manifest.json b/web/manifest.json deleted file mode 100644 index 4554ff17..00000000 --- a/web/manifest.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "TUM Campus App", - "short_name": "TCA", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "For Students by Students", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - } - ] -} diff --git a/web/splash/img/dark-1x.png b/web/splash/img/dark-1x.png deleted file mode 100644 index 9435ad44aea9726888c2a19d108f152ad6dbc66a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1440 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83c{$jCR3+ctoeT`D>7Fi*Ar*{oZz|@ABr>!m zx(H7w6}hf_g>hB1vxA@xSGTtMi*Fit7}(dn-wY0IRF`f={A z{QceHK%uSWMMa;ozI80!cKhwu9kOTcZ9MYw<;&fxSFb(~lr1bQ{P?lF=is@X&Hpd| z$lE)2Z*us#bLZmLU4Q-mZ~M*Zy9>7cIr;J9$AcVmUIQKQNqi^<>X3-}QAg4+o8H+J zwRT?b?SF4%cN@)nfAS6Qf&GpQ2Xr4d)cpNCYcF%c^&weUU?iD32Bl8V-u=h7m;p;( N22WQ%mvv4FO#n0<8bSa7 diff --git a/web/splash/img/dark-2x.png b/web/splash/img/dark-2x.png deleted file mode 100644 index e8331874f04616c0df02c361cd7995b1e429eaff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3646 zcmeAS@N?(olHy`uVBq!ia0y~yU;#3jq&e7tROy{J4;dKvSUp`FLn;{G-rkrw+mWH| zVf=zascZ!g1%q~ZEOa>LF;R&*r6qoX;U$($4!=aSY#O+~pHw^b@KnMbj@HSa?f7M~ z>;)e6iEpvx<7Tiazma41KE3}z$-4@M2P@ycVFX%pV81$$WN>HzkqnGXAd-axL<%T? z$We*WAfY5N94L#u?!Vc6gU;jXh~vl4K9Q_!Km72vMfv9PD{sqo^FOYcvzPgS%(1z~JG(!)F);iSf1LNz^4+xGH?1wsRxExi#`r+Ureg8ltbEpn(+~7P$#^s|gF<06 ze~*@Iz*I0=rjM5Cz+^C5CW4B@(K2zgOa!I^V#-AQowjoQ`_uaw7#`TLFfhawnVviQ vMv#GFPWxy=0*1h7w*iz4h)GB!hE+Y|9}SnCFF*ge2eQD^)z4*}Q$iB}#&{F8 diff --git a/web/splash/img/dark-3x.png b/web/splash/img/dark-3x.png deleted file mode 100644 index df6f239c6ea04acd6d08ec5160d1805ce3af81f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6937 zcmeAS@N?(olHy`uVBq!ia0y~yU;{FlH90`kq0%2l3=C2aJY5_^Dj46~G0c*7WM~WI zF6j`_EMk#Ym~hr}juLyMV^@sw1nwo}8{Iu79$I?(j?+A+(0~nG2P5R)A6)cJzwK%V z{}y|`at4My&ocLJ(TPV^jes z1V+PQG(CVqU^HJq(!rnzhg<7k|4RMFA?25zIn$=OZF%9medpyI55LseJ9{rPL*CJC zxvwkcrN3_|K6dxr>mNTXs@XaFAKUz$W6N|da@y0s75mQKb3FX7z~avg+oraUM}Jm+ zV>9|!WnQTJ`0kc-_ZS$qr`+Ba|5)!^^*26?^q9BPtQi|#|9D)ko_}L6!-1_oxD~)* zF(`s!w74FP3t$M0R^Q-cFd7b{%{fp)7;V-2Spa|`yI5R;6+Mt8aJe&~zr~wQNqhT-_ f2B6$99Mge@fuD=yjNB`eKpymT^>bP0l+XkKR@&nZ diff --git a/web/splash/img/dark-4x.png b/web/splash/img/dark-4x.png deleted file mode 100644 index b6e238b6109fc218ad948b7f72c83e5b2af6f8fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11472 zcmeAS@N?(olHy`uVBq!ia0y~y-~cjN%sJSAR8B=-AOnNWbWaz@kP5~(cQ1M}J2JRl zjP+ogmcTh}f$ELMr5UQXI;TzG+rnbqAsldp-%()B(`Pag-@gCl71m2Bmk?uMXt=*S z`uf+hZyXk(_dWs*J>XxqnH@-jLA^SR)xf|AW3qsp00s&UPzD17BNL3tApm1JG{9I4 zOe`>_fC7v)Y9ufwN5gV75sl^^V39CdY%q+Lmkgs71p~vt){w^;9%O#wxG~}LcV?Z_ zuPuJxR8DZ#J7ziCmWiPuKkgp?hyQ)=7k7h1`}Qy1drpo~{nT8R_&3)|_s+6q+V-fx z!bXRQ;m3w@_XEod-EH)N&4PxtJEh-GYrm=dK&03F($gO{=k75|7umjBG94^r{W7iQ zUHbcmwI9S`ErWp#fzg)CXjwd376bF*Xrq0A%i__JXtX36Er~`;B2c?%v`xe?I$$(9 zUL&LjZuKDN#O?9+#Mpn)<2p%LBNV>ZQ7F4>l;`{+eV`$5-=%_mPn%| z65UFq(J@kBP>+t0j`que$zZfKJX-evlfmfpDV7Fi*Ar*{oZz|@ABr>!m zx(H7w6}hf_g>hB1vxA@xSGTtMi*Fit7}(dn-wY0IRF`f={A z{QceHK%uSWMMa;ozI80!cKhwu9kOTcZ9MYw<;&fxSFb(~lr1bQ{P?lF=is@X&Hpd| z$lE)2Z*us#bLZmLU4Q-mZ~M*Zy9>7cIr;J9$AcVmUIQKQNqi^<>X3-}QAg4+o8H+J zwRT?b?SF4%cN@)nfAS6Qf&GpQ2Xr4d)cpNCYcF%c^&weUU?iD32Bl8V-u=h7m;p;( N22WQ%mvv4FO#n0<8bSa7 diff --git a/web/splash/img/light-2x.png b/web/splash/img/light-2x.png deleted file mode 100644 index e8331874f04616c0df02c361cd7995b1e429eaff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3646 zcmeAS@N?(olHy`uVBq!ia0y~yU;#3jq&e7tROy{J4;dKvSUp`FLn;{G-rkrw+mWH| zVf=zascZ!g1%q~ZEOa>LF;R&*r6qoX;U$($4!=aSY#O+~pHw^b@KnMbj@HSa?f7M~ z>;)e6iEpvx<7Tiazma41KE3}z$-4@M2P@ycVFX%pV81$$WN>HzkqnGXAd-axL<%T? z$We*WAfY5N94L#u?!Vc6gU;jXh~vl4K9Q_!Km72vMfv9PD{sqo^FOYcvzPgS%(1z~JG(!)F);iSf1LNz^4+xGH?1wsRxExi#`r+Ureg8ltbEpn(+~7P$#^s|gF<06 ze~*@Iz*I0=rjM5Cz+^C5CW4B@(K2zgOa!I^V#-AQowjoQ`_uaw7#`TLFfhawnVviQ vMv#GFPWxy=0*1h7w*iz4h)GB!hE+Y|9}SnCFF*ge2eQD^)z4*}Q$iB}#&{F8 diff --git a/web/splash/img/light-3x.png b/web/splash/img/light-3x.png deleted file mode 100644 index df6f239c6ea04acd6d08ec5160d1805ce3af81f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6937 zcmeAS@N?(olHy`uVBq!ia0y~yU;{FlH90`kq0%2l3=C2aJY5_^Dj46~G0c*7WM~WI zF6j`_EMk#Ym~hr}juLyMV^@sw1nwo}8{Iu79$I?(j?+A+(0~nG2P5R)A6)cJzwK%V z{}y|`at4My&ocLJ(TPV^jes z1V+PQG(CVqU^HJq(!rnzhg<7k|4RMFA?25zIn$=OZF%9medpyI55LseJ9{rPL*CJC zxvwkcrN3_|K6dxr>mNTXs@XaFAKUz$W6N|da@y0s75mQKb3FX7z~avg+oraUM}Jm+ zV>9|!WnQTJ`0kc-_ZS$qr`+Ba|5)!^^*26?^q9BPtQi|#|9D)ko_}L6!-1_oxD~)* zF(`s!w74FP3t$M0R^Q-cFd7b{%{fp)7;V-2Spa|`yI5R;6+Mt8aJe&~zr~wQNqhT-_ f2B6$99Mge@fuD=yjNB`eKpymT^>bP0l+XkKR@&nZ diff --git a/web/splash/img/light-4x.png b/web/splash/img/light-4x.png deleted file mode 100644 index b6e238b6109fc218ad948b7f72c83e5b2af6f8fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11472 zcmeAS@N?(olHy`uVBq!ia0y~y-~cjN%sJSAR8B=-AOnNWbWaz@kP5~(cQ1M}J2JRl zjP+ogmcTh}f$ELMr5UQXI;TzG+rnbqAsldp-%()B(`Pag-@gCl71m2Bmk?uMXt=*S z`uf+hZyXk(_dWs*J>XxqnH@-jLA^SR)xf|AW3qsp00s&UPzD17BNL3tApm1JG{9I4 zOe`>_fC7v)Y9ufwN5gV75sl^^V39CdY%q+Lmkgs71p~vt){w^;9%O#wxG~}LcV?Z_ zuPuJxR8DZ#J7ziCmWiPuKkgp?hyQ)=7k7h1`}Qy1drpo~{nT8R_&3)|_s+6q+V-fx z!bXRQ;m3w@_XEod-EH)N&4PxtJEh-GYrm=dK&03F($gO{=k75|7umjBG94^r{W7iQ zUHbcmwI9S`ErWp#fzg)CXjwd376bF*Xrq0A%i__JXtX36Er~`;B2c?%v`xe?I$$(9 zUL&LjZuKDN#O?9+#Mpn)<2p%LBNV>ZQ7F4>l;`{+eV`$5-=%_mPn%| z65UFq(J@kBP>+t0j`que$zZfKJX-evlfmfpDV