diff --git a/changelog.txt b/changelog.txt index fa1c4dca..36119f61 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,3 @@ This version provides the following updates: -- A responsive design for iPad, Tablets and Web App (more improvements coming soon) -- A web app which offers all functionalities -- Several bug fixes -- general UI improvements -- Temporarily the digital student card \ No newline at end of file +- Add Localisation which enables German and English +- Bugfix where users were not able to enter their TUM id while logging in \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7e6c47ca..9ffed23f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -96,7 +96,7 @@ SPEC CHECKSUMS: geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401 map_launcher: e325db1261d029ff33e08e03baccffe09593ffea package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 - path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8ff1e84f..68f04b29 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..a6b826db 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + CFBundleLocalizations + + en + de + CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 00000000..73c11ea0 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/base/localization/l10n +template-arb-file: app_en.arb +untranslated-messages-file: lib/base/localization/l10n/translation_todo.json +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/lib/base/classes/location.dart b/lib/base/classes/location.dart index 60a30077..e79fbb85 100644 --- a/lib/base/classes/location.dart +++ b/lib/base/classes/location.dart @@ -9,7 +9,8 @@ class Location { Location({required this.latitude, required this.longitude}); - factory Location.fromJson(Map json) => _$LocationFromJson(json); + factory Location.fromJson(Map json) => + _$LocationFromJson(json); Map toJson() => _$LocationToJson(this); -} \ No newline at end of file +} diff --git a/lib/base/enums/campus.dart b/lib/base/enums/campus.dart index bd3f6436..b95ced2d 100644 --- a/lib/base/enums/campus.dart +++ b/lib/base/enums/campus.dart @@ -18,17 +18,23 @@ extension CampusExtension on Campus { Location get location { switch (this) { case Campus.stammgelaende: - return Location(latitude: 48.14887567648079, longitude: 11.568029074814328); + return Location( + latitude: 48.14887567648079, longitude: 11.568029074814328); case Campus.olympiapark: - return Location(latitude: 48.17957305879896, longitude: 11.546601863009668); + return Location( + latitude: 48.17957305879896, longitude: 11.546601863009668); case Campus.klinikumRechts: - return Location(latitude: 48.13760759635786, longitude: 11.60083902677729); + return Location( + latitude: 48.13760759635786, longitude: 11.60083902677729); case Campus.grosshadern: - return Location(latitude: 48.1116433849602, longitude: 11.47027262422505); + return Location( + latitude: 48.1116433849602, longitude: 11.47027262422505); case Campus.garching: - return Location(latitude: 48.26513710129958, longitude: 11.671590834492283); + return Location( + latitude: 48.26513710129958, longitude: 11.671590834492283); case Campus.freising: - return Location(latitude: 48.39549985559942, longitude: 11.727904526510946); + return Location( + latitude: 48.39549985559942, longitude: 11.727904526510946); } } @@ -42,32 +48,38 @@ extension CampusExtension on Campus { return Station( name: "Technische Universität", apiName: "91000095", - location: Location(latitude: 48.148145129847244, longitude: 11.566048520744298)); + location: Location( + latitude: 48.148145129847244, longitude: 11.566048520744298)); case Campus.olympiapark: return Station( name: "Olympiazentrum", apiName: "91000350", - location: Location(latitude: 48.17946648767361, longitude: 11.555783595899824)); + location: Location( + latitude: 48.17946648767361, longitude: 11.555783595899824)); case Campus.klinikumRechts: return Station( name: "Max-Weber-Platz", apiName: "91000580", - location: Location(latitude: 48.13573243097588, longitude: 11.599014647301777)); + location: Location( + latitude: 48.13573243097588, longitude: 11.599014647301777)); case Campus.grosshadern: return Station( name: "Klinikum Großhadern", apiName: "91001540", - location: Location(latitude: 48.10889880944028, longitude: 11.47363212095666)); + location: Location( + latitude: 48.10889880944028, longitude: 11.47363212095666)); case Campus.garching: return Station( name: "Forschungszentrum", apiName: "1000460", - location: Location(latitude: 48.26519145730091, longitude: 11.671545161597082)); + location: Location( + latitude: 48.26519145730091, longitude: 11.671545161597082)); case Campus.freising: return Station( name: "Freising, Weihenstephan", apiName: "1002911", - location: Location(latitude: 48.39799498961109, longitude: 11.723989661968458)); + location: Location( + latitude: 48.39799498961109, longitude: 11.723989661968458)); } } @@ -79,11 +91,13 @@ extension CampusExtension on Campus { Station( name: "Theresienstraße", apiName: "91000120", - location: Location(latitude: 48.1512235719802, longitude: 11.564211669898931)), + location: Location( + latitude: 48.1512235719802, longitude: 11.564211669898931)), Station( name: "Pinakotheken", apiName: "91000051", - location: Location(latitude: 48.148780089472, longitude: 11.571870970398924)) + location: Location( + latitude: 48.148780089472, longitude: 11.571870970398924)) ]; case Campus.olympiapark: return [defaultStation]; @@ -93,7 +107,8 @@ extension CampusExtension on Campus { Station( name: "Friedensengel/Villa Stuck", apiName: "91000073", - location: Location(latitude: 48.14074544433942, longitude: 11.600075277341709)), + location: Location( + latitude: 48.14074544433942, longitude: 11.600075277341709)), ]; case Campus.grosshadern: return [ @@ -101,11 +116,13 @@ extension CampusExtension on Campus { Station( name: "Klinikum Großhadern Ost", apiName: "91001472", - location: Location(latitude: 48.11092668280441, longitude: 11.473909030506093)), + location: Location( + latitude: 48.11092668280441, longitude: 11.473909030506093)), Station( name: "Klinikum Großhadern Nord", apiName: "91001474", - location: Location(latitude: 48.11250562334001, longitude: 11.467122898318992)) + location: Location( + latitude: 48.11250562334001, longitude: 11.467122898318992)) ]; case Campus.garching: return [ @@ -113,7 +130,8 @@ extension CampusExtension on Campus { Station( name: "Lichtenbergstraße", apiName: "1002070", - location: Location(latitude: 48.26777168760462, longitude: 11.665502685140389)) + location: Location( + latitude: 48.26777168760462, longitude: 11.665502685140389)) ]; case Campus.freising: return [ @@ -121,11 +139,13 @@ extension CampusExtension on Campus { Station( name: "Freising, Forstzentrum", apiName: "1009413", - location: Location(latitude: 48.39924842116169, longitude: 11.716601891310122)), + location: Location( + latitude: 48.39924842116169, longitude: 11.716601891310122)), Station( name: "Freising, Weihenstephaner Berg", apiName: "1002617", - location: Location(latitude: 48.39581877364193, longitude: 11.725859432987532)) + location: Location( + latitude: 48.39581877364193, longitude: 11.725859432987532)) ]; } } diff --git a/lib/base/enums/gender.dart b/lib/base/enums/gender.dart index afd2ef7b..e5098fc9 100644 --- a/lib/base/enums/gender.dart +++ b/lib/base/enums/gender.dart @@ -1,6 +1 @@ -enum Gender { - male, - female, - nonBinary, - unknown -} \ No newline at end of file +enum Gender { male, female, nonBinary, unknown } diff --git a/lib/base/enums/home_widget.dart b/lib/base/enums/home_widget.dart index 5c8f735e..cf666931 100644 --- a/lib/base/enums/home_widget.dart +++ b/lib/base/enums/home_widget.dart @@ -1 +1 @@ -enum HomeWidget { cafeteria, studyRoom, calendar, departures } \ No newline at end of file +enum HomeWidget { cafeteria, studyRoom, calendar, departures } diff --git a/lib/base/enums/role.dart b/lib/base/enums/role.dart index 993fa726..5ec5e333 100644 --- a/lib/base/enums/role.dart +++ b/lib/base/enums/role.dart @@ -6,4 +6,4 @@ enum Role { final String name; const Role(this.name); -} \ No newline at end of file +} diff --git a/lib/base/extensions/cast.dart b/lib/base/extensions/cast.dart index ac9c2774..0b73d881 100644 --- a/lib/base/extensions/cast.dart +++ b/lib/base/extensions/cast.dart @@ -1 +1 @@ -T? cast(x) => x is T ? x : null; \ No newline at end of file +T? cast(x) => x is T ? x : null; diff --git a/lib/base/extensions/date_time_week_number.dart b/lib/base/extensions/date_time_week_number.dart index ffc317f0..31d44fc6 100644 --- a/lib/base/extensions/date_time_week_number.dart +++ b/lib/base/extensions/date_time_week_number.dart @@ -10,7 +10,7 @@ extension NumberOfWeeks on DateTime { int weekNumber() { int dayOfYear = int.parse(DateFormat("D").format(this)); - int woy = ((dayOfYear - weekday + 10) / 7).floor(); + int woy = ((dayOfYear - weekday + 10) / 7).floor(); if (woy < 1) { woy = _numOfWeeks(year - 1); } else if (woy > _numOfWeeks(year)) { @@ -18,4 +18,4 @@ extension NumberOfWeeks on DateTime { } return woy; } -} \ No newline at end of file +} diff --git a/lib/base/extensions/locale+fullname.dart b/lib/base/extensions/locale+fullname.dart new file mode 100644 index 00000000..a3eaa948 --- /dev/null +++ b/lib/base/extensions/locale+fullname.dart @@ -0,0 +1,13 @@ +import 'dart:ui'; + +extension FullName on Locale { + String fullName() { + switch (languageCode) { + case 'en': + return 'English'; + case 'de': + return 'Deutsch'; + } + return 'fullName not defined'; + } +} diff --git a/lib/base/helpers/card_with_padding.dart b/lib/base/helpers/card_with_padding.dart index d7ef5ba8..4fb75a4d 100644 --- a/lib/base/helpers/card_with_padding.dart +++ b/lib/base/helpers/card_with_padding.dart @@ -6,7 +6,8 @@ class CardWithPadding extends StatelessWidget { final EdgeInsetsGeometry? margin; final double? height; - const CardWithPadding({super.key, required this.child, this.color, this.margin, this.height}); + const CardWithPadding( + {super.key, required this.child, this.color, this.margin, this.height}); @override Widget build(BuildContext context) { diff --git a/lib/base/helpers/delayed_loading_indicator.dart b/lib/base/helpers/delayed_loading_indicator.dart index dc7a2c7b..3c4eb773 100644 --- a/lib/base/helpers/delayed_loading_indicator.dart +++ b/lib/base/helpers/delayed_loading_indicator.dart @@ -1,13 +1,15 @@ +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; class DelayedLoadingIndicator extends StatelessWidget { const DelayedLoadingIndicator({ super.key, - this.name, - this.alternativeLoadingIndicator, this.delayWidget = const SizedBox.shrink() + required this.name, + this.alternativeLoadingIndicator, + this.delayWidget = const SizedBox.shrink(), }); - final String? name; + final String name; final Widget? alternativeLoadingIndicator; final Widget delayWidget; @@ -22,18 +24,15 @@ class DelayedLoadingIndicator extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const CircularProgressIndicator.adaptive(), - Text(name != null ? "Loading $name" : "Loading") - ] - ) - ); + const CircularProgressIndicator.adaptive(), + Text(context.localizations.loading(name)) + ])); } else { return alternativeLoadingIndicator!; } } else { return delayWidget; } - } - ); + }); } -} \ No newline at end of file +} diff --git a/lib/base/helpers/horizontal_slider.dart b/lib/base/helpers/horizontal_slider.dart index 1cf67785..d3968713 100644 --- a/lib/base/helpers/horizontal_slider.dart +++ b/lib/base/helpers/horizontal_slider.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; class HorizontalSlider extends StatelessWidget { const HorizontalSlider( - {super.key, required this.data, required this.height, required this.child}); + {super.key, + required this.data, + required this.height, + required this.child}); final List data; final Widget Function(E data) child; @@ -14,24 +17,26 @@ class HorizontalSlider extends StatelessWidget { return SizedBox( height: height, child: Padding( - padding: EdgeInsets.only(right: orientation == Orientation.landscape ? 10 : 0), - child: ListView(scrollDirection: Axis.horizontal, children: [ - for (var indexAndValue in data.indexed) ...[ - if (indexAndValue.$1 == 0) ...[ - const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), - child(indexAndValue.$2) - ], - if (indexAndValue.$1 == data.length - 1) ...[ - const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), - child(indexAndValue.$2), - if (orientation != Orientation.landscape) - const Padding(padding: EdgeInsets.symmetric(horizontal: 5)) - ], - if (indexAndValue.$1 != 0 && indexAndValue.$1 != data.length - 1) ...[ - const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), - child(indexAndValue.$2) - ] - ] - ]))); + padding: EdgeInsets.only( + right: orientation == Orientation.landscape ? 10 : 0), + child: ListView(scrollDirection: Axis.horizontal, children: [ + for (var indexAndValue in data.indexed) ...[ + if (indexAndValue.$1 == 0) ...[ + const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), + child(indexAndValue.$2) + ], + if (indexAndValue.$1 == data.length - 1) ...[ + const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), + child(indexAndValue.$2), + if (orientation != Orientation.landscape) + const Padding(padding: EdgeInsets.symmetric(horizontal: 5)) + ], + if (indexAndValue.$1 != 0 && + indexAndValue.$1 != data.length - 1) ...[ + const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), + child(indexAndValue.$2) + ] + ] + ]))); } } diff --git a/lib/base/helpers/hyperlink_text.dart b/lib/base/helpers/hyperlink_text.dart index d986dd27..26b25a0b 100644 --- a/lib/base/helpers/hyperlink_text.dart +++ b/lib/base/helpers/hyperlink_text.dart @@ -36,9 +36,7 @@ class _HyperlinkTextState extends ConsumerState { RichText( text: TextSpan( text: widget.label, - style: Theme.of(context) - .textTheme - .bodyMedium, + style: Theme.of(context).textTheme.bodyMedium, recognizer: tapGestureRecognizer ..onTap = () { if (widget.link != null) { diff --git a/lib/base/helpers/icon_text.dart b/lib/base/helpers/icon_text.dart index 031ac931..90315f12 100644 --- a/lib/base/helpers/icon_text.dart +++ b/lib/base/helpers/icon_text.dart @@ -1,18 +1,17 @@ import 'package:flutter/material.dart'; class IconText extends StatelessWidget { - const IconText({ - super.key, - required this.iconData, - required this.label, - this.style, - this.textColor, - this.multipleLines = false, - this.leadingIcon = true, - this.mainAxisAlignment = MainAxisAlignment.start, - this.iconSize, - this.iconColor - }); + const IconText( + {super.key, + required this.iconData, + required this.label, + this.style, + this.textColor, + this.multipleLines = false, + this.leadingIcon = true, + this.mainAxisAlignment = MainAxisAlignment.start, + this.iconSize, + this.iconColor}); final IconData iconData; final String label; @@ -28,7 +27,8 @@ class IconText extends StatelessWidget { Widget build(BuildContext context) { var textStyle = style ?? TextStyle(color: textColor); var iconColor = this.iconColor ?? style?.color; - var iconSize = this.iconSize ?? (style?.fontSize != null ? style!.fontSize : 20.0); + var iconSize = + this.iconSize ?? (style?.fontSize != null ? style!.fontSize : 20.0); return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: mainAxisAlignment, @@ -37,13 +37,14 @@ class IconText extends StatelessWidget { Icon(iconData, color: iconColor, size: iconSize), const Padding(padding: EdgeInsets.symmetric(horizontal: 4.0)), multipleLines - ? Flexible(child: Text(label, style: textStyle)) - : Flexible(child: Text(label, style: textStyle, maxLines: 1)) + ? Flexible(child: Text(label, style: textStyle)) + : Flexible(child: Text(label, style: textStyle, maxLines: 1)) ], if (!leadingIcon) ...[ multipleLines ? Flexible(child: Text(label, style: textStyle)) - : Flexible(child: Text(label, + : Flexible( + child: Text(label, style: textStyle, maxLines: 1, overflow: TextOverflow.ellipsis)), diff --git a/lib/base/helpers/last_updated_text.dart b/lib/base/helpers/last_updated_text.dart index b0ad72f2..910e9bd8 100644 --- a/lib/base/helpers/last_updated_text.dart +++ b/lib/base/helpers/last_updated_text.dart @@ -1,18 +1,24 @@ +import 'package:campus_flutter/providers_get_it.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:timeago/timeago.dart' as timeago; -class LastUpdatedText extends StatelessWidget { +class LastUpdatedText extends ConsumerWidget { const LastUpdatedText(this.dateTime, {super.key}); final DateTime dateTime; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + timeago.setLocaleMessages("de", timeago.DeMessages()); return Center( - child: Text("last updated ${timeago.format(dateTime)}", + child: Text( + context.localizations.lastUpdatedAt(timeago.format(dateTime, + locale: ref.watch(locale).languageCode)), style: Theme.of(context) .textTheme .bodySmall ?.copyWith(color: Colors.grey.shade600))); } -} \ No newline at end of file +} diff --git a/lib/base/helpers/padded_divider.dart b/lib/base/helpers/padded_divider.dart index 9833b536..8140ff36 100644 --- a/lib/base/helpers/padded_divider.dart +++ b/lib/base/helpers/padded_divider.dart @@ -8,8 +8,10 @@ class PaddedDivider extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Divider(height: height,), + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Divider( + height: height, + ), ); } -} \ No newline at end of file +} diff --git a/lib/base/helpers/semester_calculator.dart b/lib/base/helpers/semester_calculator.dart index 4503db81..d6ba6afd 100644 --- a/lib/base/helpers/semester_calculator.dart +++ b/lib/base/helpers/semester_calculator.dart @@ -26,4 +26,4 @@ class SemesterCalculator { return "${year}S"; } } -} \ No newline at end of file +} diff --git a/lib/base/helpers/shimmer_view.dart b/lib/base/helpers/shimmer_view.dart index c7598dc4..3cb35eaa 100644 --- a/lib/base/helpers/shimmer_view.dart +++ b/lib/base/helpers/shimmer_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; class ShimmerView extends StatelessWidget { - final Widget child; const ShimmerView({super.key, required this.child}); @@ -13,10 +12,10 @@ class ShimmerView extends StatelessWidget { baseColor: MediaQuery.of(context).platformBrightness == Brightness.light ? Colors.grey.shade300 : Colors.grey.shade800, - highlightColor: MediaQuery.of(context).platformBrightness == Brightness.light - ? Colors.grey.shade100 - : Colors.grey.shade600, - child: child - ); + highlightColor: + MediaQuery.of(context).platformBrightness == Brightness.light + ? Colors.grey.shade100 + : Colors.grey.shade600, + child: child); } -} \ No newline at end of file +} diff --git a/lib/base/helpers/string_parser.dart b/lib/base/helpers/string_parser.dart index 09dd9c6f..3d38d562 100644 --- a/lib/base/helpers/string_parser.dart +++ b/lib/base/helpers/string_parser.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:intl/intl.dart'; class StringParser { @@ -40,10 +42,10 @@ class StringParser { // TODO: what does it stand for? return "MBD"; case "60": - // TODO: what does it stand for? + // TODO: what does it stand for? return "BECE"; case "61": - // TODO: what does it stand for? + // TODO: what does it stand for? return "BEEDE"; default: return "Unknown"; @@ -53,37 +55,23 @@ class StringParser { static String degreeShort(String degree) { // TODO: switch (degree) { - case "Bachelor of Science": return "B.Sc."; - default: return "unknown"; - } - } - - static String toFullSemesterName(String semester) { - final year = "20${semester.substring(0, 2)}"; - final nextYearShort = (int.parse(year) + 1).toString().substring(2, 4); - - switch (semester.substring(2)) { - case "W": - return "Wintersemester" " $year/$nextYearShort"; - case "S": - return "Summersemester" " $year"; + case "Bachelor of Science": + return "B.Sc."; default: - return "Unknown"; + return "unknown"; } } - static String toShortSemesterName(String semester) { - final year = "20${semester.substring(0, 2)}"; - final nextYearShort = (int.parse(year) + 1).toString().substring(2, 4); + static String toFullSemesterName(BuildContext context, String semester) { + final yearOffset = int.parse(semester.substring(0, 2)); + return context.localizations.fullSemesterName( + semester.substring(2), 2000 + yearOffset, yearOffset + 1); + } - switch (semester.substring(2)) { - case "W": - return "WiSe" " $year/$nextYearShort"; - case "S": - return "SoSe" " $year"; - default: - return "Unknown"; - } + static String toShortSemesterName(BuildContext context, String semester) { + final yearOffset = int.parse(semester.substring(0, 2)); + return context.localizations.shortSemesterName( + semester.substring(2), 2000 + yearOffset, yearOffset + 1); } static String dateFormatter(DateTime dateTime) { @@ -114,10 +102,9 @@ class StringParser { } else { return 0; } - } static int? optStringToOptInt(String? number) { return number != null ? int.tryParse(number) : null; } -} \ No newline at end of file +} diff --git a/lib/base/localization/l10n/app_de.arb b/lib/base/localization/l10n/app_de.arb new file mode 100644 index 00000000..857a4d08 --- /dev/null +++ b/lib/base/localization/l10n/app_de.arb @@ -0,0 +1,188 @@ +{ + "home":"Start", + "grades":"Noten", + "lectures":"Vorlesungen", + "calendar":"Kalender", + "identification":"Identifikation", + "places":"Orte", + "settings":"Einstellungen", + "generalSettings":"Allgemeine Einstellungen", + "comingSoon":"demnächst verfügbar", + "language":"Sprache", + "useWebView":"Web-Ansicht verwenden", + "contactUs":"Kontaktiere uns", + "tokenPermissions":"Berechtigungen für Token", + "permissionChangePossibleInTUMonline":"Du kannst deine Berechtigungen in TUMOnline ändern", + "logout":"Abmelden", + "login":"Anmelden", + "notLoggedIn":"Nicht angemeldet", + "welcomeToTheApp":"Willkommen in der TUM-Campus-App", + "enterYourIDToStart":"Gib deine TUM ID ein, um zu starten", + "continueWithoutID":"Ohne TUM ID fortfahren", + "unknown":"Unbekannt", + "loading":"Laden von {name}", + "@loading":{ + "description":"Während Ladeanimationen angezeigt", + "placeholders":{ + "name":{ + "type":"String", + "example":"Noten" + } + } + }, + "checkPermissions":"Berechtigungen prüfen", + "done":"Fertig", + "nearestStudyRooms":"Nächste Lernräume", + "noNearFreeStudyRoomsFound":"Keine Lernräume in deiner Nähe gefunden", + "noMoviesFound":"Keine Filme gefunden", + "tuition":"Tuition", + "tuitionFees":"Studiengebühren", + "tuitionPaid":"Bezahlt", + "tuitionDueDate":"Fälligkeitsdatum", + "tuitionOpenAmount":"Offener Betrag", + "versionNumber":"Version {number}", + "@versionNumber":{ + "description":"Aktuelle Version der App", + "placeholders":{ + "number":{ + "type":"String", + "example":"-.-.-" + } + } + }, + "checkToken":"Token überprüfen", + "tryAgain":"Erneut versuchen", + "contactSupport":"Support kontaktieren", + "cafeteria":"Mensa", + "noMealPlanFound":"Kein Speiseplan gefunden", + "latestNews":"Neueste Nachrichten", + "scheduledLectureDates":"Termine", + "@scheduledLectureDates":{ + "description":"Link-Text in der Ansicht der Vorlesungslinks" + }, + "lectureExamDate":"Prüfung", + "@lectureExamDate":{ + "description":"Link-Text in der Ansicht der Vorlesungslinks" + }, + "lectureCurriculum":"Lehrplan", + "@lectureCurriculum":{ + "description":"Link-Text in der Ansicht der Vorlesungslinks" + }, + "basicLectureInformation":"Grundlegende Vorlesungsinformation", + "detailedLectureInformation":"Detaillierte Vorlesungsinformation", + "lectureLinks":"Vorlesungslinks", + "thisMeeting":"Dieses Treffen", + "lectureDetails":"Vorlesungsdetails", + "noLecturesSelected":"keine vorlesungen ausgewählt", + "noLecturesFound":"keine vorlesungen gefunden", + "showDirections":"Wegbeschreibung anzeigen", + "notAvailableAbbrev":"n. v.", + "becomeABetaTester":"Werde Beta-Tester", + "usOnGitHub":"TUM-Dev auf GitHub", + "lastUpdatedAt":"zuletzt aktualisiert {lastUpdate}", + "@lastUpdatedAt":{ + "description":"Wann etwas zuletzt aktualisiert wurde", + "placeholders":{ + "lastUpdate":{ + "type":"String", + "format":"vor 42 Sekunden" + } + } + }, + "averageGrade":"Durchschnittsnote", + "fullSemesterName":"{semesterType, select, W{Wintersemester {year}/{nextYearShort}} S{Sommersemester {year}} other{Unbekannt}}", + "@fullSemesterName":{ + "description":"Bezeichnung des Semesters", + "placeholders":{ + "semesterType":{ + "type":"String", + "example":"W" + }, + "year":{ + "type":"int", + "example":"2023" + }, + "nextYearShort":{ + "type":"int", + "example":"2024" + } + } + }, + "shortSemesterName":"{semesterType, select, W{WiSe {year}/{nextYearShort}} S{SoSe {year}} other{}}", + "@shortSemesterName":{ + "description":"Kurzbezeichnung des Semesters", + "placeholders":{ + "semesterType":{ + "type":"String", + "example":"W" + }, + "year":{ + "type":"int", + "example":"2023" + }, + "nextYearShort":{ + "type":"int", + "example":"2024" + } + } + }, + "lecture":"Vorlesung", + "exercise":"Tutorium", + "seminar":"Übung", + "tutorial":"Praktikum", + "practicalCourse":"Seminar", + "lectureWithIntegratedExcercises":"Vorlesung mit integrierten Übungen", + "written":"Schriftlich", + "graded":"Beurteilt", + "writtenAndOral":"Schriftlich/Mündlich", + "oral":"Mündlich", + "source":"Quelle: {source}", + "@source":{ + "description":"Woher dies stammt", + "placeholders":{ + "source":{ + "type":"String?", + "example":"Newspread Live CH magistrale" + } + } + }, + "noNewsFound":"Keine Nachrichten gefunden", + "news":"Nachrichten", + "movies":"Filme", + "personalData":"Persönliche Daten", + "noGradesFound":"Keine Noten gefunden", + "calendarViewToday":"Heute", + "calendarViewDay":"Tag", + "calendarViewWeek":"Woche", + "calendarViewMonth":"Monat", + "noEventsToday":"Keine Ereignisse heute", + "departures":"Abfahrten", + "departure":"Abfahrt", + "direction":"Richtung", + "station":"Haltestelle: ", + "line":"Linie", + "now":"JETZT", + "free":"Frei", + "occupiedUntil":"Belegt bis {occupiedEndTime}", + "@occupiedUntil":{ + "description":"time until occupied", + "placeholders":{ + "occupiedEndTime":{ + "type":"DateTime", + "format":"Hm" + } + } + }, + "rooms":"Räume", + "departures":"Abfahrten", + "nfreeRooms":"{count, plural, =0{Keine freien Räume} =1{1 freier Raum} other{{count} freie Räume}}", + "@nfreeRooms":{ + "description":"How many free rooms there are", + "placeholders":{ + "count":{ + "type":"num", + "format":"compact" + } + } + } +} \ No newline at end of file diff --git a/lib/base/localization/l10n/app_en.arb b/lib/base/localization/l10n/app_en.arb new file mode 100644 index 00000000..73a50c01 --- /dev/null +++ b/lib/base/localization/l10n/app_en.arb @@ -0,0 +1,188 @@ +{ + "home":"Home", + "grades":"Grades", + "lectures":"Lectures", + "calendar":"Calendar", + "identification":"Identification", + "places":"Places", + "settings":"Settings", + "generalSettings":"General Settings", + "comingSoon":"Coming Soon", + "language":"Language", + "useWebView":"Use Web View", + "contactUs":"Contact Us", + "tokenPermissions":"Token Permissions", + "permissionChangePossibleInTUMonline":"You can change your permissions on TUMOnline", + "logout":"Logout", + "login":"Login", + "notLoggedIn":"Not Logged In", + "welcomeToTheApp":"Welcome to the TUM Campus App", + "enterYourIDToStart":"Enter your TUM ID to get started", + "continueWithoutID":"Continue without TUM ID", + "unknown":"Unknown", + "loading":"Loading {name}", + "@loading":{ + "description":"Shown during loading animations", + "placeholders":{ + "name":{ + "type":"String", + "example":"Grades" + } + } + }, + "checkPermissions":"Check Permissions", + "done":"Done", + "nearestStudyRooms":"Nearest Study Rooms", + "noNearFreeStudyRoomsFound":"no study rooms near you found", + "noMoviesFound":"no movies found", + "tuition":"Tuition", + "tuitionFees":"Tuition fees", + "tuitionPaid":"Tuition Paid", + "tuitionDueDate":"Due Date", + "tuitionOpenAmount":"Open Amount", + "versionNumber":"Version {number}", + "@versionNumber":{ + "description":"What version number the app is currently on", + "placeholders":{ + "number":{ + "type":"String", + "example":"-.-.-" + } + } + }, + "checkToken":"Check Token", + "tryAgain":"Try Again", + "contactSupport":"Contact Support", + "cafeteria":"Cafeteria", + "noMealPlanFound":"no meal plan found", + "latestNews":"Latest News", + "scheduledLectureDates":"Dates", + "@scheduledLectureDates":{ + "description":"Link-text in the lecture links view" + }, + "lectureExamDate":"Exam", + "@lectureExamDate":{ + "description":"Link-text in the lecture links view" + }, + "lectureCurriculum":"Curriculum", + "@lectureCurriculum":{ + "description":"Link-text in the lecture links view" + }, + "basicLectureInformation":"Basic Lecture Information", + "detailedLectureInformation":"Detailed Lecture Information", + "lectureLinks":"Lecture Links", + "thisMeeting":"This Meeting", + "lectureDetails":"Lecture Details", + "noLecturesSelected":"no lecture selected", + "noLecturesFound":"no lectures found", + "showDirections":"Show Directions", + "notAvailableAbbrev":"n/a", + "becomeABetaTester":"Become a Beta Tester", + "usOnGitHub":"TUM-Dev on GitHub", + "lastUpdatedAt":"last updated {lastUpdate}", + "@lastUpdatedAt":{ + "description":"When something was last updated", + "placeholders":{ + "lastUpdate":{ + "type":"String", + "format":"42 seconds ago" + } + } + }, + "averageGrade":"Average Grade", + "fullSemesterName":"{semesterType, select, W{Wintersemester {year}/{nextYearShort}} S{Summersemester {year}} other{Unknown}}", + "@fullSemesterName":{ + "description":"When something was last updated", + "placeholders":{ + "semesterType":{ + "type":"String", + "example":"W" + }, + "year":{ + "type":"int", + "example":"2023" + }, + "nextYearShort":{ + "type":"int", + "example":"2024" + } + } + }, + "shortSemesterName":"{semesterType, select, W{WiSe {year}/{nextYearShort}} S{SoSe {year}} other{}}", + "@shortSemesterName":{ + "description":"When something was last updated", + "placeholders":{ + "semesterType":{ + "type":"String", + "example":"W" + }, + "year":{ + "type":"int", + "example":"2023" + }, + "nextYearShort":{ + "type":"int", + "example":"2024" + } + } + }, + "lecture":"Lecture", + "exercise":"Exercise", + "seminar":"Seminar", + "tutorial":"Exercise", + "practicalCourse":"Practical course", + "lectureWithIntegratedExcercises":"Lecture with integrated Exercises", + "written":"Written", + "graded":"Graded", + "writtenAndOral":"WrittenOral", + "oral":"Oral", + "source":"Source: {source}", + "@source":{ + "description":"Where this is from", + "placeholders":{ + "source":{ + "type":"String?", + "example":"Newspread Live CH magistrale" + } + } + }, + "noNewsFound":"no news found", + "news":"News", + "movies":"Movies", + "personalData":"Personal Data", + "noGradesFound":"no grades found", + "calendarViewToday":"Today", + "calendarViewDay":"Day", + "calendarViewWeek":"Week", + "calendarViewMonth":"Month", + "noEventsToday":"No Events Today", + "departures":"Departures", + "departure":"Departure", + "direction":"Direction", + "station":"Station: ", + "line":"Line", + "now":"NOW", + "free":"Free", + "occupiedUntil":"Occupied until {occupiedEndTime}", + "@occupiedUntil":{ + "description":"time until occupied", + "placeholders":{ + "occupiedEndTime":{ + "type":"DateTime", + "format":"Hm" + } + } + }, + "rooms":"Rooms", + "departures":"Departures", + "nfreeRooms":"{count, plural, =0{no free rooms} =1{1 free room} other{{count} free rooms}}", + "@nfreeRooms":{ + "description":"How many free rooms there are", + "placeholders":{ + "count":{ + "type":"num", + "format":"compact" + } + } + } +} \ No newline at end of file diff --git a/lib/base/localization/l10n/translation_todo.json b/lib/base/localization/l10n/translation_todo.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/lib/base/localization/l10n/translation_todo.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/lib/base/networking/apis/eatApi/eat_api.dart b/lib/base/networking/apis/eatApi/eat_api.dart index c331ce0a..f790d7ac 100644 --- a/lib/base/networking/apis/eatApi/eat_api.dart +++ b/lib/base/networking/apis/eatApi/eat_api.dart @@ -2,7 +2,6 @@ import 'package:campus_flutter/base/networking/apis/eatApi/eat_api_service.dart' import 'package:campus_flutter/base/networking/protocols/api.dart'; class EatApi extends Api { - final EatApiService eatApiService; EatApi(this.eatApiService); @@ -36,4 +35,4 @@ class EatApi extends Api { @override bool get needsAuth => false; -} \ No newline at end of file +} diff --git a/lib/base/networking/apis/mvvDeparturesApi/mvv_departures_api.dart b/lib/base/networking/apis/mvvDeparturesApi/mvv_departures_api.dart index 429230e2..7fdeb188 100644 --- a/lib/base/networking/apis/mvvDeparturesApi/mvv_departures_api.dart +++ b/lib/base/networking/apis/mvvDeparturesApi/mvv_departures_api.dart @@ -1,7 +1,6 @@ import 'package:campus_flutter/base/networking/protocols/api.dart'; class MvvDeparturesApi extends Api { - final String station; final int? walkingTime; @@ -15,25 +14,25 @@ class MvvDeparturesApi extends Api { @override Map get parameters => { - "outputFormat": "JSON", - "language": "en", - "stateless": "1", - "coordOutputFormat": "WGS84", - "type_dm": "stop", - "name_dm": station, - if (walkingTime != null) "timeOffset": walkingTime.toString(), - "useRealtime": "1", - "itOptionsActive": "1", - "ptOptionsActive": "1", - "limit": "20", - "mergeDep": "1", - "useAllStops": "1", - "mode": "direct" - }; + "outputFormat": "JSON", + "language": "en", + "stateless": "1", + "coordOutputFormat": "WGS84", + "type_dm": "stop", + "name_dm": station, + if (walkingTime != null) "timeOffset": walkingTime.toString(), + "useRealtime": "1", + "itOptionsActive": "1", + "ptOptionsActive": "1", + "limit": "20", + "mergeDep": "1", + "useAllStops": "1", + "mode": "direct" + }; @override String get path => "/ng/"; @override String get paths => "${path}XML_DM_REQUEST"; -} \ No newline at end of file +} diff --git a/lib/base/networking/apis/navigaTumApi/navigatum_api.dart b/lib/base/networking/apis/navigaTumApi/navigatum_api.dart index 8f6bd4c9..9a6fcad6 100644 --- a/lib/base/networking/apis/navigaTumApi/navigatum_api.dart +++ b/lib/base/networking/apis/navigaTumApi/navigatum_api.dart @@ -2,7 +2,6 @@ import 'package:campus_flutter/base/networking/apis/navigaTumApi/navigatum_api_s import 'package:campus_flutter/base/networking/protocols/api.dart'; class NavigaTumApi extends Api { - final NavigaTumService navigaTumService; NavigaTumApi({required this.navigaTumService}); @@ -32,4 +31,4 @@ class NavigaTumApi extends Api { return "cdn/maps/roomfinder/${overlayImages.id}"; } } -} \ No newline at end of file +} diff --git a/lib/base/networking/apis/navigaTumApi/navigatum_api_serivce.dart b/lib/base/networking/apis/navigaTumApi/navigatum_api_serivce.dart index 61077130..6c2ed879 100644 --- a/lib/base/networking/apis/navigaTumApi/navigatum_api_serivce.dart +++ b/lib/base/networking/apis/navigaTumApi/navigatum_api_serivce.dart @@ -8,9 +8,7 @@ class NavigaTumServiceSearch extends NavigaTumService { NavigaTumServiceSearch({required this.query}); @override - Map getParameters() => { - "q": query - }; + Map getParameters() => {"q": query}; } class NavigaTumServiceDetails extends NavigaTumService { @@ -20,9 +18,7 @@ class NavigaTumServiceDetails extends NavigaTumService { NavigaTumServiceDetails({required this.id, required this.language}); @override - Map getParameters() => { - "lang": language - }; + Map getParameters() => {"lang": language}; } class NavigaTumServiceImages extends NavigaTumService { @@ -35,4 +31,4 @@ class NavigaTumServiceOverlayImages extends NavigaTumService { final String id; NavigaTumServiceOverlayImages({required this.id}); -} \ No newline at end of file +} diff --git a/lib/base/networking/apis/tumCabeApi/tum_cabe_api.dart b/lib/base/networking/apis/tumCabeApi/tum_cabe_api.dart index 869b0c23..49dbccdc 100644 --- a/lib/base/networking/apis/tumCabeApi/tum_cabe_api.dart +++ b/lib/base/networking/apis/tumCabeApi/tum_cabe_api.dart @@ -3,7 +3,6 @@ import 'package:campus_flutter/base/networking/protocols/api.dart'; import 'package:flutter/foundation.dart' show kIsWeb; class TumCabeApi extends Api { - final TumCabeService tumCabeService; TumCabeApi({required this.tumCabeService}); @@ -64,43 +63,11 @@ class TumCabeApi extends Api { return "${path}news/sources"; case TumCabeServiceNewsAlert _: return "${path}news/alert"; - case TumCabeServiceRoomSearch _: - // TODO: - return path; - /*return "roomfinder/room/search/${roomSearch.query.addingPercentEncoding( - withAllowedCharacters: .afURLQueryAllowed) ?? ""}";*/ - case TumCabeServiceRoomMaps _: - // TODO: - return path; - /*return "roomfinder/room/availableMaps/${roomMaps.room.addingPercentEncoding( - withAllowedCharacters: .afURLQueryAllowed) ?? ""}";*/ - case TumCabeServiceRoomCoordinates roomCoordinates: - return "${path}roomfinder/room/coordinates/${roomCoordinates.room}"; - case TumCabeServiceDefaultMap defaultMap: - return "${path}roomfinder/room/defaultMap/${defaultMap.room}"; - case TumCabeServiceMapImage mapImage: - return "${path}roomfinder/room/map/${mapImage.room}/${mapImage.id}"; case TumCabeServiceRegisterDevice registerDevice: return "${path}device/register/${registerDevice.publicKey}"; - case TumCabeServiceEvents _: - return "${path}event/list"; - case TumCabeServiceMyEvents _: - return "${path}event/ticket/my"; - case TumCabeServiceTicketTypes ticketTypes: - return "${path}event/ticket/type/${ticketTypes.event}"; - case TumCabeServiceTicketStats ticketStats: - return "${path}event/ticket/type/${ticketStats.event}"; - case TumCabeServiceTicketReservation _: - return "${path}event/ticket/reserve"; - case TumCabeServiceTicketReservationCancellation _: - return "${path}event/ticket/reserve/cancel"; - case TumCabeServiceTicketPurchase _: - return "${path}event/ticket/payment/stripe/purchase"; - case TumCabeServiceStripeKey _: - return "${path}event/ticket/payment/stripe/ephemeralkey"; } } @override Map get parameters => {}; -} \ No newline at end of file +} diff --git a/lib/base/networking/apis/tumCabeApi/tum_cabe_api_service.dart b/lib/base/networking/apis/tumCabeApi/tum_cabe_api_service.dart index 7a1f5338..8af73651 100644 --- a/lib/base/networking/apis/tumCabeApi/tum_cabe_api_service.dart +++ b/lib/base/networking/apis/tumCabeApi/tum_cabe_api_service.dart @@ -16,63 +16,8 @@ class TumCabeServiceNewsSources extends TumCabeService {} class TumCabeServiceNewsAlert extends TumCabeService {} -class TumCabeServiceRoomSearch extends TumCabeService { - final String query; - - TumCabeServiceRoomSearch({required this.query}); -} - -class TumCabeServiceRoomMaps extends TumCabeService { - final String room; - - TumCabeServiceRoomMaps({required this.room}); -} - -class TumCabeServiceRoomCoordinates extends TumCabeService { - final String room; - - TumCabeServiceRoomCoordinates({required this.room}); -} - -class TumCabeServiceMapImage extends TumCabeService { - final int id; - final String room; - - TumCabeServiceMapImage({required this.id, required this.room}); -} - -class TumCabeServiceDefaultMap extends TumCabeService { - final String room; - - TumCabeServiceDefaultMap({required this.room}); -} - class TumCabeServiceRegisterDevice extends TumCabeService { final String publicKey; TumCabeServiceRegisterDevice({required this.publicKey}); } - -class TumCabeServiceEvents extends TumCabeService {} - -class TumCabeServiceMyEvents extends TumCabeService {} - -class TumCabeServiceTicketTypes extends TumCabeService { - final int event; - - TumCabeServiceTicketTypes({required this.event}); -} - -class TumCabeServiceTicketStats extends TumCabeService { - final int event; - - TumCabeServiceTicketStats({required this.event}); -} - -class TumCabeServiceTicketReservation extends TumCabeService {} - -class TumCabeServiceTicketReservationCancellation extends TumCabeService {} - -class TumCabeServiceTicketPurchase extends TumCabeService {} - -class TumCabeServiceStripeKey extends TumCabeService {} \ No newline at end of file diff --git a/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api.dart b/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api.dart index c66f4bf4..aec18c29 100644 --- a/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api.dart +++ b/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api.dart @@ -3,7 +3,6 @@ import 'package:campus_flutter/base/networking/protocols/api.dart'; // TODO: figure out errors class TumDevAppApi extends Api { - final TumDevAppService tumDevAppService; TumDevAppApi({required this.tumDevAppService}); @@ -22,4 +21,4 @@ class TumDevAppApi extends Api { @override String get paths => "iris/ris_api.php"; -} \ No newline at end of file +} diff --git a/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api_service.dart b/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api_service.dart index f1fe6696..71c00fbb 100644 --- a/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api_service.dart +++ b/lib/base/networking/apis/tumDevAppApi/tum_dev_app_api_service.dart @@ -8,15 +8,11 @@ class TumDevAppServiceRoom extends TumDevAppService { TumDevAppServiceRoom({required this.roomNr}); @override - Map getParameters() => { - "format": "json", - "raum": roomNr.toString() - }; + Map getParameters() => + {"format": "json", "raum": roomNr.toString()}; } class TumDevAppServiceRooms extends TumDevAppService { @override - Map getParameters() => { - "format": "json" - }; -} \ No newline at end of file + Map getParameters() => {"format": "json"}; +} diff --git a/lib/base/networking/apis/tumOnlineApi/tum_online_api_exception.dart b/lib/base/networking/apis/tumOnlineApi/tum_online_api_exception.dart index 45d0e86c..3b3e1a52 100644 --- a/lib/base/networking/apis/tumOnlineApi/tum_online_api_exception.dart +++ b/lib/base/networking/apis/tumOnlineApi/tum_online_api_exception.dart @@ -16,7 +16,8 @@ class TumOnlineApiException implements ApiException { tumOnlineApiExceptionType: TumOnlineApiExceptionNoPermission()); case "Token ist nicht bestätigt!": return TumOnlineApiException( - tumOnlineApiExceptionType: TumOnlineApiExceptionTokenNotConfirmed()); + tumOnlineApiExceptionType: + TumOnlineApiExceptionTokenNotConfirmed()); case "Token ist ungültig!": return TumOnlineApiException( tumOnlineApiExceptionType: TumOnlineApiExceptionInvalidToken()); @@ -26,11 +27,12 @@ class TumOnlineApiException implements ApiException { tumOnlineApiExceptionType: TumOnlineApiExceptionPersonNotFound()); case String message when message.contains("Suchstring"): return TumOnlineApiException( - tumOnlineApiExceptionType: TumOnlineApiExceptionInvalidSearchString()); + tumOnlineApiExceptionType: + TumOnlineApiExceptionInvalidSearchString()); default: return TumOnlineApiException( - tumOnlineApiExceptionType: - TumOnlineApiExceptionUnknown(message: exception.exceptionMessage.message)); + tumOnlineApiExceptionType: TumOnlineApiExceptionUnknown( + message: exception.exceptionMessage.message)); } } @@ -92,7 +94,8 @@ sealed class TumOnlineApiExceptionType {} class TumOnlineApiExceptionNoPermission extends TumOnlineApiExceptionType {} -class TumOnlineApiExceptionTokenNotConfirmed extends TumOnlineApiExceptionType {} +class TumOnlineApiExceptionTokenNotConfirmed + extends TumOnlineApiExceptionType {} class TumOnlineApiExceptionInvalidToken extends TumOnlineApiExceptionType {} @@ -102,9 +105,11 @@ class TumOnlineApiExceptionNoUserSpecified extends TumOnlineApiExceptionType {} class TumOnlineApiExceptionPersonNotFound extends TumOnlineApiExceptionType {} -class TumOnlineApiExceptionInvalidSearchString extends TumOnlineApiExceptionType {} +class TumOnlineApiExceptionInvalidSearchString + extends TumOnlineApiExceptionType {} -class TumOnlineApiExceptionTokenLimitReached extends TumOnlineApiExceptionType {} +class TumOnlineApiExceptionTokenLimitReached + extends TumOnlineApiExceptionType {} class TumOnlineApiExceptionNoUserFound extends TumOnlineApiExceptionType {} diff --git a/lib/base/networking/apis/tumOnlineApi/tum_online_api_service.dart b/lib/base/networking/apis/tumOnlineApi/tum_online_api_service.dart index f45bc97c..90481b4c 100644 --- a/lib/base/networking/apis/tumOnlineApi/tum_online_api_service.dart +++ b/lib/base/networking/apis/tumOnlineApi/tum_online_api_service.dart @@ -6,16 +6,11 @@ class TumOnlineServiceTokenRequest extends TumOnlineService { final String tumId; final String deviceName; - TumOnlineServiceTokenRequest({ - required this.tumId, - required this.deviceName - }); + TumOnlineServiceTokenRequest({required this.tumId, required this.deviceName}); @override - Map getParameters() => { - "pUsername": tumId, - "pTokenName": deviceName - }; + Map getParameters() => + {"pUsername": tumId, "pTokenName": deviceName}; } class TumOnlineServiceTokenConfirmation extends TumOnlineService {} @@ -30,9 +25,7 @@ class TumOnlineServicePersonSearch extends TumOnlineService { TumOnlineServicePersonSearch({required this.search}); @override - Map getParameters() => { - "pSuche": search - }; + Map getParameters() => {"pSuche": search}; } class TumOnlineServicePersonDetails extends TumOnlineService { @@ -41,9 +34,7 @@ class TumOnlineServicePersonDetails extends TumOnlineService { TumOnlineServicePersonDetails({required this.identNumber}); @override - Map getParameters() => { - "pIdentNr": identNumber - }; + Map getParameters() => {"pIdentNr": identNumber}; } class TumOnlineServicePersonalLectures extends TumOnlineService {} @@ -58,9 +49,7 @@ class TumOnlineServiceLectureSearch extends TumOnlineService { TumOnlineServiceLectureSearch({required this.search}); @override - Map getParameters() => { - "pSuche": search - }; + Map getParameters() => {"pSuche": search}; } class TumOnlineServiceLectureDetails extends TumOnlineService { @@ -69,9 +58,7 @@ class TumOnlineServiceLectureDetails extends TumOnlineService { TumOnlineServiceLectureDetails({required this.lvNr}); @override - Map getParameters() => { - "pLVNr": lvNr - }; + Map getParameters() => {"pLVNr": lvNr}; } class TumOnlineServiceIdentify extends TumOnlineService {} @@ -87,8 +74,6 @@ class TumOnlineServiceProfileImage extends TumOnlineService { TumOnlineServiceProfileImage({required this.personGroup, required this.id}); @override - Map getParameters() => { - "pPersonenGruppe": personGroup, - "pPersonenId": id - }; -} \ No newline at end of file + Map getParameters() => + {"pPersonenGruppe": personGroup, "pPersonenId": id}; +} diff --git a/lib/base/networking/apis/tumSexyApi/tum_sexy_api.dart b/lib/base/networking/apis/tumSexyApi/tum_sexy_api.dart index e486d866..caad4dc8 100644 --- a/lib/base/networking/apis/tumSexyApi/tum_sexy_api.dart +++ b/lib/base/networking/apis/tumSexyApi/tum_sexy_api.dart @@ -15,4 +15,4 @@ class TumSexyApi extends Api { @override String get paths => ""; -} \ No newline at end of file +} diff --git a/lib/base/networking/protocols/api.dart b/lib/base/networking/protocols/api.dart index 61c72f63..2a734895 100644 --- a/lib/base/networking/protocols/api.dart +++ b/lib/base/networking/protocols/api.dart @@ -1,14 +1,15 @@ import 'package:dio/dio.dart' as dio; abstract class Api { - static String tumToken = ""; String get baseURL; String get path; - Map get baseHeaders { return {}; } + Map get baseHeaders { + return {}; + } String get paths; @@ -22,10 +23,12 @@ abstract class Api { // TODO: figure out token sharing finalParameters.addAll({"pToken": tumToken}); final uri = Uri.https(baseURL, paths, finalParameters); - return dioClient.getUri(uri, options: _customDecodingOptions(baseHeaders)); + return dioClient.getUri(uri, + options: _customDecodingOptions(baseHeaders)); } else { final uri = Uri.https(baseURL, paths, parameters); - return dioClient.getUri(uri, options: _customDecodingOptions(baseHeaders)); + return dioClient.getUri(uri, + options: _customDecodingOptions(baseHeaders)); } } diff --git a/lib/base/networking/protocols/api_exception.dart b/lib/base/networking/protocols/api_exception.dart index 4c3c8ac2..5880b91a 100644 --- a/lib/base/networking/protocols/api_exception.dart +++ b/lib/base/networking/protocols/api_exception.dart @@ -14,7 +14,8 @@ class ExceptionBody { ExceptionBody({required this.exceptionMessage}); - factory ExceptionBody.fromJson(Map json) => _$ExceptionBodyFromJson(json); + factory ExceptionBody.fromJson(Map json) => + _$ExceptionBodyFromJson(json); Map toJson() => _$ExceptionBodyToJson(this); } @@ -25,7 +26,8 @@ class ExceptionMessage { ExceptionMessage({required this.message}); - factory ExceptionMessage.fromJson(Map json) => _$ExceptionMessageFromJson(json); + factory ExceptionMessage.fromJson(Map json) => + _$ExceptionMessageFromJson(json); Map toJson() => _$ExceptionMessageToJson(this); -} \ No newline at end of file +} diff --git a/lib/base/networking/protocols/api_response.dart b/lib/base/networking/protocols/api_response.dart index 837fca65..87745b38 100644 --- a/lib/base/networking/protocols/api_response.dart +++ b/lib/base/networking/protocols/api_response.dart @@ -8,19 +8,15 @@ class ApiResponse { ApiResponse({required this.data, this.saved}); factory ApiResponse.fromJson( - dynamic json, - Headers headers, - Function(Map) create) { + dynamic json, Headers headers, Function(Map) create) { if (json is List) { return ApiResponse( - data: create({"data": json}), - saved: ApiResponse._parseDate(headers["date"]?.first) - ); + data: create({"data": json}), + saved: ApiResponse._parseDate(headers["date"]?.first)); } else { return ApiResponse( data: create(json), - saved: ApiResponse._parseDate(headers["date"]?.first) - ); + saved: ApiResponse._parseDate(headers["date"]?.first)); } } @@ -37,4 +33,4 @@ class ApiResponse { return null; } } -} \ No newline at end of file +} diff --git a/lib/base/networking/protocols/custom_cache_interceptor.dart b/lib/base/networking/protocols/custom_cache_interceptor.dart index 4f3b11c4..becbb18d 100644 --- a/lib/base/networking/protocols/custom_cache_interceptor.dart +++ b/lib/base/networking/protocols/custom_cache_interceptor.dart @@ -5,12 +5,12 @@ 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}); + CustomCacheInterceptor( + {required this.cacheStore, required this.cacheOptions}); @override void onError(DioException err, ErrorInterceptorHandler handler) { @@ -18,20 +18,24 @@ class CustomCacheInterceptor implements Interceptor { } @override - Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async { + Future onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { ConnectivityResult connectivityResult = getIt(); options.extra[CacheResponse.requestSentDate] = DateTime.now(); final key = CacheOptions.defaultCacheKeyBuilder(options); /// checks if device has a working internet connection - if (connectivityResult != ConnectivityResult.none && !options.path.contains("tumCard")) { + if (connectivityResult != ConnectivityResult.none && + !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) { + 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 @@ -41,7 +45,8 @@ class CustomCacheInterceptor implements Interceptor { } else { /// if device is online, the cache is valid for 30 days final cache = await cacheStore.get(key); - if (cache != null && DateTime.now().difference(cache.responseDate).inDays <= 30) { + if (cache != null && + DateTime.now().difference(cache.responseDate).inDays <= 30) { return handler.resolve(cache.toResponse(options, fromNetwork: false)); } else { cacheStore.delete(key); @@ -51,10 +56,13 @@ class CustomCacheInterceptor implements Interceptor { } @override - Future onResponse(Response response, ResponseInterceptorHandler handler) async { + 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)); + final cacheResponse = await CacheResponse.fromResponse( + key: key, options: cacheOptions, response: response); + await cacheStore.set( + await cacheResponse.writeContent(cacheOptions, response: response)); handler.next(response); } -} \ No newline at end of file +} diff --git a/lib/base/networking/protocols/view_model.dart b/lib/base/networking/protocols/view_model.dart index c52379df..6841bf26 100644 --- a/lib/base/networking/protocols/view_model.dart +++ b/lib/base/networking/protocols/view_model.dart @@ -1,3 +1,3 @@ abstract class ViewModel { Future fetch(bool forcedRefresh); -} \ No newline at end of file +} diff --git a/lib/base/services/location_service.dart b/lib/base/services/location_service.dart index 4d7d7009..8a099d9a 100644 --- a/lib/base/services/location_service.dart +++ b/lib/base/services/location_service.dart @@ -37,4 +37,4 @@ class LocationService { return await Geolocator.getLastKnownPosition(); } } -} \ No newline at end of file +} diff --git a/lib/base/views/error_handling_view.dart b/lib/base/views/error_handling_view.dart index 2b949d15..7948a908 100644 --- a/lib/base/views/error_handling_view.dart +++ b/lib/base/views/error_handling_view.dart @@ -1,5 +1,6 @@ import 'package:campus_flutter/base/networking/apis/tumOnlineApi/tum_online_api_exception.dart'; import 'package:dio/dio.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; class ErrorHandlingView extends StatelessWidget { @@ -91,7 +92,8 @@ class ErrorHandlingView extends StatelessWidget { const Spacer(), if (retry != null) ...[ ElevatedButton( - onPressed: () => retry!(true), child: const Text("Try Again")), + onPressed: () => retry!(true), + child: Text(context.localizations.tryAgain)), const Spacer() ] ])); diff --git a/lib/base/views/generic_stream_builder.dart b/lib/base/views/generic_stream_builder.dart index d6adce97..191ae309 100644 --- a/lib/base/views/generic_stream_builder.dart +++ b/lib/base/views/generic_stream_builder.dart @@ -29,4 +29,4 @@ class GenericStreamBuilder extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/calendarComponent/model/calendar_data_source.dart b/lib/calendarComponent/model/calendar_data_source.dart index be6c68b0..723da276 100644 --- a/lib/calendarComponent/model/calendar_data_source.dart +++ b/lib/calendarComponent/model/calendar_data_source.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; class MeetingDataSource extends CalendarDataSource { - final BuildContext context; MeetingDataSource(List source, this.context) { @@ -35,4 +34,4 @@ class MeetingDataSource extends CalendarDataSource { Color getColor(int index) { return Theme.of(context).primaryColor; } -} \ No newline at end of file +} diff --git a/lib/calendarComponent/viewModels/calendar_viewmodel.dart b/lib/calendarComponent/viewModels/calendar_viewmodel.dart index 3ac1f5fa..8fa5f692 100644 --- a/lib/calendarComponent/viewModels/calendar_viewmodel.dart +++ b/lib/calendarComponent/viewModels/calendar_viewmodel.dart @@ -24,15 +24,17 @@ class CalendarViewModel implements ViewModel { List rightColumn = []; final filteredEvents = events.value ?? []; - filteredEvents.removeWhere((element) => element.startDate.isBefore(DateTime.now())); + filteredEvents + .removeWhere((element) => element.startDate.isBefore(DateTime.now())); filteredEvents.sort((a, b) => a.startDate.compareTo(b.startDate)); final currentDate = DateTime.now(); - final currentDay = DateTime(currentDate.year, currentDate.month, currentDate.day); + final currentDay = + DateTime(currentDate.year, currentDate.month, currentDate.day); for (CalendarEvent event in events.value ?? []) { - final dateToCheck = - DateTime(event.startDate.year, event.startDate.month, event.startDate.day); + final dateToCheck = DateTime( + event.startDate.year, event.startDate.month, event.startDate.day); if (dateToCheck == currentDay && leftColumn == null) { leftColumn = event; } else if (rightColumn.length <= 2 && @@ -46,4 +48,4 @@ class CalendarViewModel implements ViewModel { return (leftColumn, rightColumn); } -} \ No newline at end of file +} diff --git a/lib/calendarComponent/views/calendar_month_view.dart b/lib/calendarComponent/views/calendar_month_view.dart index 5d5289bb..a8fba092 100644 --- a/lib/calendarComponent/views/calendar_month_view.dart +++ b/lib/calendarComponent/views/calendar_month_view.dart @@ -27,4 +27,4 @@ class CalendarMonthView extends ConsumerWidget { }, )); } -} \ No newline at end of file +} diff --git a/lib/calendarComponent/views/calendars_view.dart b/lib/calendarComponent/views/calendars_view.dart index 3c04456a..ac6e2a34 100644 --- a/lib/calendarComponent/views/calendars_view.dart +++ b/lib/calendarComponent/views/calendars_view.dart @@ -11,6 +11,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:campus_flutter/theme.dart'; class CalendarsView extends ConsumerStatefulWidget { const CalendarsView({super.key}); @@ -24,12 +25,6 @@ class _CalendarsViewState extends ConsumerState { final CalendarController _calendarController = CalendarController(); - final Map calendarTabs = const { - 0: Text("Day"), - 1: Text("Week"), - 2: Text("Month") - }; - @override void initState() { ref.read(calendarViewModel).fetch(false); @@ -55,12 +50,17 @@ class _CalendarsViewState extends ConsumerState { _calendarController.displayDate = DateTime.now(); }); }, - child: Text("Today", + child: Text(context.localizations.calendarViewToday, style: Theme.of(context).textTheme.titleMedium)), - const Padding(padding: EdgeInsets.symmetric(horizontal: 4.0)), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 4.0)), Expanded( child: CupertinoSlidingSegmentedControl( - children: calendarTabs, + children: { + 0: Text(context.localizations.calendarViewDay), + 1: Text(context.localizations.calendarViewWeek), + 2: Text(context.localizations.calendarViewMonth) + }, onValueChanged: (i) { setState(() { _selectedCalendarTab = i ?? 0; @@ -69,7 +69,7 @@ class _CalendarsViewState extends ConsumerState { groupValue: _selectedCalendarTab)) ], )), - if(lastFetched != null) LastUpdatedText(lastFetched), + if (lastFetched != null) LastUpdatedText(lastFetched), [ CalendarDayView(calendarController: _calendarController), const CalendarWeekView(), @@ -82,9 +82,10 @@ class _CalendarsViewState extends ConsumerState { errorHandlingViewType: ErrorHandlingViewType.fullScreen, retry: ref.read(calendarViewModel).fetch); } else { - return const DelayedLoadingIndicator(name: "Calendar"); + return DelayedLoadingIndicator( + name: context.localizations.calendar); } - }); + }); } } diff --git a/lib/calendarComponent/views/homeWidget/calendar_widget_event_view.dart b/lib/calendarComponent/views/homeWidget/calendar_widget_event_view.dart index f0ddbd6a..2706ae58 100644 --- a/lib/calendarComponent/views/homeWidget/calendar_widget_event_view.dart +++ b/lib/calendarComponent/views/homeWidget/calendar_widget_event_view.dart @@ -13,14 +13,16 @@ class CalendarHomeWidgetEventView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final String startTime = DateFormat(DateFormat.HOUR24_MINUTE).format(calendarEvent.startDate); - final String endTime = DateFormat(DateFormat.HOUR24_MINUTE).format(calendarEvent.endDate); + final String startTime = + DateFormat(DateFormat.HOUR24_MINUTE).format(calendarEvent.startDate); + final String endTime = + DateFormat(DateFormat.HOUR24_MINUTE).format(calendarEvent.endDate); final DateTime today = DateTime.now(); final DateTime todayDate = DateTime(today.year, today.month, today.day); - final DateTime tomorrowDate = - DateTime(today.year, today.month, today.day).add(const Duration(days: 1)); - final DateTime startDate = DateTime( - calendarEvent.startDate.year, calendarEvent.startDate.month, calendarEvent.startDate.day); + final DateTime tomorrowDate = DateTime(today.year, today.month, today.day) + .add(const Duration(days: 1)); + final DateTime startDate = DateTime(calendarEvent.startDate.year, + calendarEvent.startDate.month, calendarEvent.startDate.day); return GestureDetector( onTap: () { @@ -29,7 +31,10 @@ class CalendarHomeWidgetEventView extends ConsumerWidget { } else { ref.read(selectedEvent.notifier).state = calendarEvent; ref.read(selectedLecture.notifier).state = null; - ref.read(homeSplitViewModel).selectedWidget.add(const LectureDetailsView()); + ref + .read(homeSplitViewModel) + .selectedWidget + .add(const LectureDetailsView()); } }, child: Column( @@ -41,13 +46,17 @@ class CalendarHomeWidgetEventView extends ConsumerWidget { ? "Today" : startDate.isAtSameMomentAs(tomorrowDate) ? "Tomorrow" - : DateFormat("EEEE, d. MMM").format(calendarEvent.startDate), - style: TextStyle(color: Theme.of(context).colorScheme.secondary)), + : DateFormat("EEEE, d. MMM") + .format(calendarEvent.startDate), + style: + TextStyle(color: Theme.of(context).colorScheme.secondary)), const Spacer(), Container( decoration: BoxDecoration( border: Border( - left: BorderSide(color: Theme.of(context).primaryColor, width: 2.0))), + left: BorderSide( + color: Theme.of(context).primaryColor, + width: 2.0))), child: Padding( padding: const EdgeInsets.only(left: 5.0), child: Column( diff --git a/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart b/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart index e63fd0f0..aa795b99 100644 --- a/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart +++ b/lib/calendarComponent/views/homeWidget/calendar_widget_view.dart @@ -8,6 +8,7 @@ import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:campus_flutter/theme.dart'; class CalendarHomeWidgetView extends ConsumerStatefulWidget { const CalendarHomeWidgetView({super.key}); @@ -78,7 +79,7 @@ class _CalendarHomeWidgetView extends ConsumerState { Expanded( child: (events.$1 != null) ? CalendarHomeWidgetEventView(calendarEvent: events.$1!) - : const Center(child: Text("No Events Today"))) + : Center(child: Text(context.localizations.noEventsToday))) ])), const Padding(padding: EdgeInsets.symmetric(horizontal: 5.0)), Expanded( diff --git a/lib/departuresComponent/model/departure.dart b/lib/departuresComponent/model/departure.dart index 862ce4c8..86806df8 100644 --- a/lib/departuresComponent/model/departure.dart +++ b/lib/departuresComponent/model/departure.dart @@ -17,16 +17,16 @@ class Departure { final ServingLine servingLine; final LineInfosType? lineInfos; - Departure({ - required this.stopId, - required this.countdown, - required this.dateTime, - this.realDateTime, - required this.servingLine, - this.lineInfos - }); + Departure( + {required this.stopId, + required this.countdown, + required this.dateTime, + this.realDateTime, + required this.servingLine, + this.lineInfos}); - factory Departure.fromJson(Map json) => _$DepartureFromJson(json); + factory Departure.fromJson(Map json) => + _$DepartureFromJson(json); Map toJson() => _$DepartureToJson(this); @@ -36,8 +36,7 @@ class Departure { int.parse(json["month"]), int.parse(json["day"]), int.parse(json["hour"]), - int.parse(json["minute"]) - ); + int.parse(json["minute"])); } static DateTime? realDate(Map? json) { @@ -47,8 +46,7 @@ class Departure { int.parse(json["month"]), int.parse(json["day"]), int.parse(json["hour"]), - int.parse(json["minute"]) - ); + int.parse(json["minute"])); } else { return null; } @@ -60,9 +58,11 @@ class DepartureDateTime { @JsonKey(fromJson: StringParser.stringToInt) final int year, month, day, weekday, hour, minute; - DepartureDateTime(this.year, this.month, this.day, this.weekday, this.hour, this.minute); + DepartureDateTime( + this.year, this.month, this.day, this.weekday, this.hour, this.minute); - factory DepartureDateTime.fromJson(Map json) => _$DepartureDateTimeFromJson(json); + factory DepartureDateTime.fromJson(Map json) => + _$DepartureDateTimeFromJson(json); Map toJson() => _$DepartureDateTimeToJson(this); } @@ -75,22 +75,22 @@ class ServingLine { @JsonKey(fromJson: StringParser.optStringToOptInt) final int? delay; - ServingLine({ - required this.key, - required this.code, - required this.number, - required this.symbol, - required this.direction, - required this.name, - this.delay - }); + ServingLine( + {required this.key, + required this.code, + required this.number, + required this.symbol, + required this.direction, + required this.name, + this.delay}); - factory ServingLine.fromJson(Map json) => _$ServingLineFromJson(json); + factory ServingLine.fromJson(Map json) => + _$ServingLineFromJson(json); Map toJson() => _$ServingLineToJson(this); Color get color { - switch(code) { + switch (code) { case 3: return const Color.fromRGBO(1, 83, 102, 1.0); case 1: @@ -124,7 +124,10 @@ class LineInfosType { factory LineInfosType.fromJson(Map json) { if (json['lineInfos'] != null) { - return LineInfosType(array: (json['lineInfos'] as List).map((e) => LineInfoContent.fromJson(e)).toList()); + return LineInfosType( + array: (json['lineInfos'] as List) + .map((e) => LineInfoContent.fromJson(e)) + .toList()); } else if (json['lineInfo'] != null) { return LineInfosType(element: LineInfoElement.fromJson(json)); } else { @@ -133,9 +136,9 @@ class LineInfosType { } Map toJson() => { - 'array': array?.map((e) => e.toJson()).toList(), - 'element': element?.toJson(), - }; + 'array': array?.map((e) => e.toJson()).toList(), + 'element': element?.toJson(), + }; } @JsonSerializable() @@ -144,7 +147,8 @@ class LineInfoElement { LineInfoElement({required this.lineInfo}); - factory LineInfoElement.fromJson(Map json) => _$LineInfoElementFromJson(json); + factory LineInfoElement.fromJson(Map json) => + _$LineInfoElementFromJson(json); Map toJson() => _$LineInfoElementToJson(this); } @@ -157,7 +161,8 @@ class LineInfoContent { LineInfoContent({this.infoLinkText, this.infoText, this.additionalLinks}); - factory LineInfoContent.fromJson(Map json) => _$LineInfoContentFromJson(json); + factory LineInfoContent.fromJson(Map json) => + _$LineInfoContentFromJson(json); Map toJson() => _$LineInfoContentToJson(this); } @@ -168,15 +173,15 @@ class AdditionalLink { final String id; final String linkURL, linkText, linkTextShort, linkTarget; - AdditionalLink({ - required this.id, - required this.linkURL, - required this.linkText, - required this.linkTextShort, - required this.linkTarget - }); + AdditionalLink( + {required this.id, + required this.linkURL, + required this.linkText, + required this.linkTextShort, + required this.linkTarget}); - factory AdditionalLink.fromJson(Map json) => _$AdditionalLinkFromJson(json); + factory AdditionalLink.fromJson(Map json) => + _$AdditionalLinkFromJson(json); Map toJson() => _$AdditionalLinkToJson(this); } @@ -185,12 +190,10 @@ class AdditionalLink { class InfoText { final String content, subtitle; - InfoText({ - required this.content, - required this.subtitle - }); + InfoText({required this.content, required this.subtitle}); - factory InfoText.fromJson(Map json) => _$InfoTextFromJson(json); + factory InfoText.fromJson(Map json) => + _$InfoTextFromJson(json); Map toJson() => _$InfoTextToJson(this); -} \ No newline at end of file +} diff --git a/lib/departuresComponent/model/departures_preference.dart b/lib/departuresComponent/model/departures_preference.dart index 5eac585f..09a1982f 100644 --- a/lib/departuresComponent/model/departures_preference.dart +++ b/lib/departuresComponent/model/departures_preference.dart @@ -10,7 +10,8 @@ class DeparturesPreference { DeparturesPreference({required this.preferences}); - factory DeparturesPreference.fromJson(Map json) => _$DeparturesPreferenceFromJson(json); + factory DeparturesPreference.fromJson(Map json) => + _$DeparturesPreferenceFromJson(json); Map toJson() => _$DeparturesPreferenceToJson(this); -} \ No newline at end of file +} diff --git a/lib/departuresComponent/model/mvv_response.dart b/lib/departuresComponent/model/mvv_response.dart index 0e318049..b6e34c47 100644 --- a/lib/departuresComponent/model/mvv_response.dart +++ b/lib/departuresComponent/model/mvv_response.dart @@ -10,7 +10,8 @@ class MvvResponse { MvvResponse({required this.departures}); - factory MvvResponse.fromJson(Map json) => _$MvvResponseFromJson(json); + factory MvvResponse.fromJson(Map json) => + _$MvvResponseFromJson(json); Map toJson() => _$MvvResponseToJson(this); } diff --git a/lib/departuresComponent/model/station.dart b/lib/departuresComponent/model/station.dart index 1d70cb2d..25d6e9c5 100644 --- a/lib/departuresComponent/model/station.dart +++ b/lib/departuresComponent/model/station.dart @@ -4,19 +4,17 @@ import 'package:json_annotation/json_annotation.dart'; part 'station.g.dart'; @JsonSerializable() + /// local data type class Station { final String name; final String apiName; final Location? location; - Station({ - required this.name, - required this.apiName, - this.location - }); + Station({required this.name, required this.apiName, this.location}); - factory Station.fromJson(Map json) => _$StationFromJson(json); + factory Station.fromJson(Map json) => + _$StationFromJson(json); Map toJson() => _$StationToJson(this); -} \ No newline at end of file +} diff --git a/lib/departuresComponent/services/departures_service.dart b/lib/departuresComponent/services/departures_service.dart index 1b91a2ed..0219f54c 100644 --- a/lib/departuresComponent/services/departures_service.dart +++ b/lib/departuresComponent/services/departures_service.dart @@ -4,14 +4,14 @@ import 'package:campus_flutter/departuresComponent/model/mvv_response.dart'; import 'package:campus_flutter/providers_get_it.dart'; class DeparturesService { - static Future<({DateTime? saved, MvvResponse data})> fetchDepartures(bool forcedRefresh, String station, int? walkingTime) async { + static Future<({DateTime? saved, MvvResponse data})> fetchDepartures( + bool forcedRefresh, String station, int? walkingTime) async { MainApi mainApi = getIt(); final response = await mainApi.makeRequest( MvvDeparturesApi(station: station, walkingTime: walkingTime), MvvResponse.fromJson, - forcedRefresh - ); + forcedRefresh); return (saved: response.saved, data: response.data); } -} \ No newline at end of file +} diff --git a/lib/departuresComponent/viewModel/departures_viewmodel.dart b/lib/departuresComponent/viewModel/departures_viewmodel.dart index a0816e0d..0cc637da 100644 --- a/lib/departuresComponent/viewModel/departures_viewmodel.dart +++ b/lib/departuresComponent/viewModel/departures_viewmodel.dart @@ -22,7 +22,8 @@ class DeparturesViewModel extends ViewModel { final BehaviorSubject closestCampus = BehaviorSubject.seeded(null); final BehaviorSubject walkingDistance = BehaviorSubject.seeded(null); - final BehaviorSubject selectedStation = BehaviorSubject.seeded(null); + final BehaviorSubject selectedStation = + BehaviorSubject.seeded(null); Timer? timer; @@ -42,11 +43,17 @@ class DeparturesViewModel extends ViewModel { if (location != null) { final closestCampus = Campus.values.reduce((currentCampus, nextCampus) { - final currentDistance = Geolocator.distanceBetween(currentCampus.location.latitude, - currentCampus.location.longitude, location.latitude, location.longitude); - - final nextDistance = Geolocator.distanceBetween(nextCampus.location.latitude, - nextCampus.location.longitude, location.latitude, location.longitude); + final currentDistance = Geolocator.distanceBetween( + currentCampus.location.latitude, + currentCampus.location.longitude, + location.latitude, + location.longitude); + + final nextDistance = Geolocator.distanceBetween( + nextCampus.location.latitude, + nextCampus.location.longitude, + location.latitude, + location.longitude); return currentDistance < nextDistance ? currentCampus : nextCampus; }); @@ -61,7 +68,8 @@ class DeparturesViewModel extends ViewModel { Future assignSelectedStation() async { if (closestCampus.value != null) { - final SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); + final SharedPreferences sharedPreferences = + await SharedPreferences.getInstance(); final data = sharedPreferences.get("departuresPreferences") as String?; if (data != null) { final decoded = jsonDecode(data); @@ -98,7 +106,9 @@ class DeparturesViewModel extends ViewModel { onError: (error) => departures.addError(error)); } else { DeparturesService.fetchDepartures( - true, closestCampus.value!.defaultStation.apiName, walkingDistance.value) + true, + closestCampus.value!.defaultStation.apiName, + walkingDistance.value) .then((response) => sortDepartures(response), onError: (error) => departures.addError(error)); } @@ -127,7 +137,8 @@ class DeparturesViewModel extends ViewModel { setTimerForRefresh() { if ((departures.value?.length ?? 0) > 0) { if (departures.value![0].countdown > 0) { - timer = Timer(Duration(minutes: departures.value![0].countdown), fetchDepartures); + timer = Timer( + Duration(minutes: departures.value![0].countdown), fetchDepartures); return; } } @@ -136,28 +147,29 @@ class DeparturesViewModel extends ViewModel { } void updatePreference() async { - final SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); + final SharedPreferences sharedPreferences = + await SharedPreferences.getInstance(); if (selectedStation.value != null && closestCampus.value != null) { final data = sharedPreferences.get("departuresPreferences") as String?; if (data != null) { final decodedData = jsonDecode(data); try { - DeparturesPreference preferences = DeparturesPreference.fromJson( - decodedData); + DeparturesPreference preferences = + DeparturesPreference.fromJson(decodedData); preferences.preferences[closestCampus.value!] = - selectedStation.value!; + selectedStation.value!; final json = jsonEncode(preferences.toJson()); sharedPreferences.setString("departuresPreferences", json); } catch (_) { - final DeparturesPreference preferences = - DeparturesPreference(preferences: {closestCampus.value!: selectedStation.value!}); + final DeparturesPreference preferences = DeparturesPreference( + preferences: {closestCampus.value!: selectedStation.value!}); final json = jsonEncode(preferences.toJson()); sharedPreferences.setString("departuresPreferences", json); } } else { - final DeparturesPreference preferences = - DeparturesPreference(preferences: {closestCampus.value!: selectedStation.value!}); + final DeparturesPreference preferences = DeparturesPreference( + preferences: {closestCampus.value!: selectedStation.value!}); final json = jsonEncode(preferences.toJson()); sharedPreferences.setString("departuresPreferences", json); } @@ -170,7 +182,8 @@ class DeparturesViewModel extends ViewModel { .map((e) => PopupMenuItem( value: e, child: selectedStation.value?.name == e.name - ? IconText(iconData: Icons.check, label: e.name, leadingIcon: false) + ? IconText( + iconData: Icons.check, label: e.name, leadingIcon: false) : Text(e.name))) .toList(); } else { diff --git a/lib/departuresComponent/views/departures_details_row_view.dart b/lib/departuresComponent/views/departures_details_row_view.dart index 96d4dac2..0e52a116 100644 --- a/lib/departuresComponent/views/departures_details_row_view.dart +++ b/lib/departuresComponent/views/departures_details_row_view.dart @@ -3,6 +3,7 @@ import 'package:campus_flutter/departuresComponent/model/departure.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:campus_flutter/theme.dart'; class DeparturesDetailsRowView extends ConsumerWidget { const DeparturesDetailsRowView({super.key, required this.departure}); @@ -19,7 +20,7 @@ class DeparturesDetailsRowView extends ConsumerWidget { maxLines: 1, overflow: TextOverflow.ellipsis)), _delayText, const Padding(padding: EdgeInsets.symmetric(horizontal: 5.0)), - _timeText + _timeText(context) ], ); } @@ -29,10 +30,14 @@ class DeparturesDetailsRowView extends ConsumerWidget { onTap: () async { if (departure.lineInfos != null) { if (departure.lineInfos?.element != null) { - final link = departure.lineInfos?.element?.lineInfo.additionalLinks?[0].linkURL ?? ""; + final link = departure.lineInfos?.element?.lineInfo + .additionalLinks?[0].linkURL ?? + ""; UrlLauncher.urlString(link, ref); } else if (departure.lineInfos?.array != null) { - final link = departure.lineInfos?.array?[0].additionalLinks?[0].linkURL ?? ""; + final link = + departure.lineInfos?.array?[0].additionalLinks?[0].linkURL ?? + ""; UrlLauncher.urlString(link, ref); } } @@ -44,7 +49,8 @@ class DeparturesDetailsRowView extends ConsumerWidget { padding: const EdgeInsets.only(right: 15), child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), color: departure.servingLine.color), + borderRadius: BorderRadius.circular(5), + color: departure.servingLine.color), width: 55, height: 35, child: Center( @@ -52,7 +58,9 @@ class DeparturesDetailsRowView extends ConsumerWidget { style: Theme.of(context) .textTheme .bodyLarge - ?.copyWith(color: Colors.white, fontWeight: FontWeight.bold))), + ?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold))), )), if (departure.lineInfos != null) _warningOverlay ], @@ -63,7 +71,8 @@ class DeparturesDetailsRowView extends ConsumerWidget { return const Positioned( width: 53 * 2, bottom: 22, - child: Icon(Icons.warning_outlined, color: Color(0xffFFCC01), size: 20)); + child: + Icon(Icons.warning_outlined, color: Color(0xffFFCC01), size: 20)); } Widget get _delayText { @@ -77,27 +86,29 @@ class DeparturesDetailsRowView extends ConsumerWidget { return const SizedBox.shrink(); } - Widget get _timeText { + Widget _timeText(BuildContext context) { if (departure.realDateTime != null) { final realDateTime = departure.realDateTime!; if ((departure.servingLine.delay ?? 0) > 0) { - return timeBuilder(realDateTime, Colors.red); + return timeBuilder(context, realDateTime, Colors.red); } else { - return timeBuilder(realDateTime, Colors.green); + return timeBuilder(context, realDateTime, Colors.green); } } else { - return timeBuilder(departure.dateTime, null); + return timeBuilder(context, departure.dateTime, null); } } - Widget timeBuilder(DateTime dateTime, Color? color) { + Widget timeBuilder(BuildContext context, DateTime dateTime, Color? color) { // TODO: walking distance if (departure.countdown < 1) { - return Text("NOW", style: TextStyle(color: color, fontWeight: FontWeight.w500)); + return Text(context.localizations.now, + style: TextStyle(color: color, fontWeight: FontWeight.w500)); } else { final hour = NumberFormat("00").format(dateTime.hour); final minute = NumberFormat("00").format(dateTime.minute); - return Text("$hour:$minute", style: TextStyle(color: color, fontWeight: FontWeight.w500)); + return Text("$hour:$minute", + style: TextStyle(color: color, fontWeight: FontWeight.w500)); } } } diff --git a/lib/departuresComponent/views/departures_details_view.dart b/lib/departuresComponent/views/departures_details_view.dart index 17ad9ef6..3f42f765 100644 --- a/lib/departuresComponent/views/departures_details_view.dart +++ b/lib/departuresComponent/views/departures_details_view.dart @@ -9,6 +9,7 @@ import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:map_launcher/map_launcher.dart'; +import 'package:campus_flutter/theme.dart'; class DeparturesDetailsScaffold extends ConsumerWidget { const DeparturesDetailsScaffold({super.key}); @@ -18,16 +19,17 @@ class DeparturesDetailsScaffold extends ConsumerWidget { return StreamBuilder( stream: ref.watch(departureViewModel).departures, builder: (context, snapshot) { - final backgroundColor = MediaQuery.platformBrightnessOf(context) == Brightness.dark - ? Theme.of(context).canvasColor - : Colors.white; + final backgroundColor = + MediaQuery.platformBrightnessOf(context) == Brightness.dark + ? Theme.of(context).canvasColor + : Colors.white; return Scaffold( appBar: AppBar( leading: const BackButton(), backgroundColor: backgroundColor, title: Text( ref.watch(departureViewModel).selectedStation.value?.name ?? - "Departures"), + context.localizations.departures), actions: [ PopupMenuButton( initialValue: @@ -70,7 +72,8 @@ class _DeparturesDetailsViewState extends ConsumerState { children: [ if (ref.watch(departureViewModel).selectedStation.value != null) ...[ - Text.rich(TextSpan(text: "Station: ", children: [ + Text.rich( + TextSpan(text: context.localizations.station, children: [ TextSpan( text: ref .watch(departureViewModel) @@ -85,7 +88,8 @@ class _DeparturesDetailsViewState extends ConsumerState { onTap: () async { Station? selectedStation = ref.read(departureViewModel).selectedStation.value; - if (selectedStation != null && selectedStation.location != null) { + if (selectedStation != null && + selectedStation.location != null) { if (await MapLauncher.isMapAvailable(MapType.google) ?? false) { await MapLauncher.showDirections( @@ -110,23 +114,24 @@ class _DeparturesDetailsViewState extends ConsumerState { } } }, - child: const IconText( - iconData: Icons.open_in_new, label: "Show Directions")) + child: IconText( + iconData: Icons.open_in_new, + label: context.localizations.showDirections)) ], const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)), if (lastFetched != null) LastUpdatedText(lastFetched), - const Row( + Row( children: [ SizedBox( width: 50, - child: Text("Line", - style: TextStyle(fontWeight: FontWeight.w500))), - Padding(padding: EdgeInsets.symmetric(horizontal: 7.5)), - Text("Direction", - style: TextStyle(fontWeight: FontWeight.w500)), - Spacer(), - Text("Departure", - style: TextStyle(fontWeight: FontWeight.w500)) + child: Text(context.localizations.line, + style: const TextStyle(fontWeight: FontWeight.w500))), + const Padding(padding: EdgeInsets.symmetric(horizontal: 7.5)), + Text(context.localizations.direction, + style: const TextStyle(fontWeight: FontWeight.w500)), + const Spacer(), + Text(context.localizations.departure, + style: const TextStyle(fontWeight: FontWeight.w500)) ], ), const Divider(), @@ -138,8 +143,7 @@ class _DeparturesDetailsViewState extends ConsumerState { child: ListView.separated( itemBuilder: (context, index) => DeparturesDetailsRowView( - departure: - widget.snapshot.data![index]), + departure: widget.snapshot.data![index]), separatorBuilder: (context, index) => const Divider(), itemCount: widget.snapshot.data!.length))), ], @@ -148,10 +152,9 @@ class _DeparturesDetailsViewState extends ConsumerState { return ErrorHandlingView( error: widget.snapshot.error!, errorHandlingViewType: ErrorHandlingViewType.fullScreen, - retry: ref.read(departureViewModel).fetch - ); + retry: ref.read(departureViewModel).fetch); } else { - return const DelayedLoadingIndicator(name: "Departures"); + return DelayedLoadingIndicator(name: context.localizations.departures); } } } diff --git a/lib/departuresComponent/views/homeWidget/departures_widget_view.dart b/lib/departuresComponent/views/homeWidget/departures_widget_view.dart index b85ee802..0c8a33de 100644 --- a/lib/departuresComponent/views/homeWidget/departures_widget_view.dart +++ b/lib/departuresComponent/views/homeWidget/departures_widget_view.dart @@ -7,6 +7,7 @@ import 'package:campus_flutter/departuresComponent/views/departures_details_row_ import 'package:campus_flutter/departuresComponent/views/departures_details_view.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/providers_get_it.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -14,7 +15,8 @@ class DeparturesHomeWidget extends ConsumerStatefulWidget { const DeparturesHomeWidget({super.key}); @override - ConsumerState createState() => _DeparturesHomeWidgetState(); + ConsumerState createState() => + _DeparturesHomeWidgetState(); } class _DeparturesHomeWidgetState extends ConsumerState { @@ -34,23 +36,29 @@ class _DeparturesHomeWidgetState extends ConsumerState { child: GestureDetector( onTap: () => _onWidgetPressed(context), child: ConstrainedBox( - constraints: - BoxConstraints(minHeight: MediaQuery.of(context).size.height * 0.225), + constraints: BoxConstraints( + minHeight: + MediaQuery.of(context).size.height * 0.225), child: CardWithPadding( child: StreamBuilder( stream: ref.watch(departureViewModel).departures, builder: (context, snapshot) { if (snapshot.hasData) { - final station = - ref.watch(departureViewModel).selectedStation.value!; + final station = ref + .watch(departureViewModel) + .selectedStation + .value!; return _widgetContent(snapshot, station); } else if (snapshot.hasError) { return ErrorHandlingView( error: snapshot.error!, - errorHandlingViewType: ErrorHandlingViewType.textOnly, - retry: ref.read(departureViewModel).fetch); + errorHandlingViewType: + ErrorHandlingViewType.textOnly, + retry: + ref.read(departureViewModel).fetch); } else { - return const DelayedLoadingIndicator(name: "Departures"); + return DelayedLoadingIndicator( + name: context.localizations.departures); } }))))); }); @@ -58,26 +66,28 @@ class _DeparturesHomeWidgetState extends ConsumerState { String _titleBuilder() { if (ref.watch(departureViewModel).closestCampus.value?.name != null) { - return "Departures @ ${ref.watch(departureViewModel).closestCampus.value?.name}"; + return "${context.localizations.departures} @ ${ref.watch(departureViewModel).closestCampus.value?.name}"; } else { - return "Departures"; + return context.localizations.departures; } } - Widget _widgetContent(AsyncSnapshot?> snapshot, Station station) { + Widget _widgetContent( + AsyncSnapshot?> snapshot, Station station) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( - text: "Station: ", + text: "${context.localizations.station} ", style: Theme.of(context).textTheme.bodyMedium, children: [ TextSpan( text: station.name, style: TextStyle( - color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold)) + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold)) ])), for (var departure in snapshot.data!.getRange(0, 3)) ...[ const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)), @@ -87,7 +97,7 @@ class _DeparturesHomeWidgetState extends ConsumerState { } _onWidgetPressed(BuildContext context) { - Navigator.of(context) - .push(MaterialPageRoute(builder: (context) => const DeparturesDetailsScaffold())); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const DeparturesDetailsScaffold())); } } diff --git a/lib/gradeComponent/model/grade.dart b/lib/gradeComponent/model/grade.dart index faa963b8..5d276348 100644 --- a/lib/gradeComponent/model/grade.dart +++ b/lib/gradeComponent/model/grade.dart @@ -1,4 +1,6 @@ +import 'package:flutter/cupertino.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:campus_flutter/theme.dart'; part 'grade.g.dart'; @@ -31,18 +33,18 @@ class Grade { @JsonKey(name: "st_studium_nr") final String studyNumber; - String get modusShort { + String modusShort(BuildContext context) { switch (modus) { case "Schriftlich": - return "Written"; + return context.localizations.written; case "Beurteilt/immanenter Prüfungscharakter": - return "Graded"; + return context.localizations.graded; case "Schriftlich und Mündlich": - return "Written/Oral"; + return context.localizations.writtenAndOral; case "Mündlich": - return "Oral"; + return context.localizations.oral; default: - return "Unknown"; + return modus; } } @@ -71,7 +73,8 @@ class GradeData { GradeData({required this.gradesAttribute}); - factory GradeData.fromJson(Map json) => _$GradeDataFromJson(json); + factory GradeData.fromJson(Map json) => + _$GradeDataFromJson(json); Map toJson() => _$GradeDataToJson(this); } @@ -86,4 +89,4 @@ class Grades { factory Grades.fromJson(Map json) => _$GradesFromJson(json); Map toJson() => _$GradesToJson(this); -} \ No newline at end of file +} diff --git a/lib/gradeComponent/viewModels/grade_viewmodel.dart b/lib/gradeComponent/viewModels/grade_viewmodel.dart index b5fdd1ab..15c7084a 100644 --- a/lib/gradeComponent/viewModels/grade_viewmodel.dart +++ b/lib/gradeComponent/viewModels/grade_viewmodel.dart @@ -38,7 +38,8 @@ class GradeViewModel implements ViewModel { AverageGrade? getAverageGrade() { if (studyProgramGrades.hasValue) { - return _averageGrades.firstWhereOrNull((element) => element.id == studyProgramGrades.value?.values.first.first.studyID); + return _averageGrades.firstWhereOrNull((element) => + element.id == studyProgramGrades.value?.values.first.first.studyID); } return null; } @@ -70,22 +71,21 @@ class GradeViewModel implements ViewModel { List> getMenuEntries() { if (_allGrades?.values != null) { - return _allGrades!.values - .map((e) { - final selectedStudyId = studyProgramGrades.value?.values.first.first.studyID; - final studyId = e.values.first.first.studyID; - final studyDesignation = e.values.first.first.studyDesignation; - final degree = StringParser.degreeShortFromID(studyId); - return PopupMenuItem( + return _allGrades!.values.map((e) { + final selectedStudyId = + studyProgramGrades.value?.values.first.first.studyID; + final studyId = e.values.first.first.studyID; + final studyDesignation = e.values.first.first.studyDesignation; + final degree = StringParser.degreeShortFromID(studyId); + return PopupMenuItem( value: studyId, child: selectedStudyId == studyId ? IconText( - iconData: Icons.check, - label: "$studyDesignation ($degree)", - leadingIcon: false) + iconData: Icons.check, + label: "$studyDesignation ($degree)", + leadingIcon: false) : Text("$studyDesignation ($degree)")); - }) - .toList(); + }).toList(); } else { return []; } @@ -108,21 +108,22 @@ class GradeViewModel implements ViewModel { } } - return Map.fromEntries(chartData.entries.toList()..sort((a, b) { - if (a.key is double && b.key is double) { - final aKey = a.key as double; - final bKey = b.key as double; - return aKey.compareTo(bKey); - } else if (a.key is double) { - return a.key > 4 ? 1 : -1; - } else if (b.key is double) { - return b.key > 4 ? -1 : 1; - } else { - final aKey = a.key as String; - final bKey = b.key as String; - return aKey.compareTo(bKey); - } - })); + return Map.fromEntries(chartData.entries.toList() + ..sort((a, b) { + if (a.key is double && b.key is double) { + final aKey = a.key as double; + final bKey = b.key as double; + return aKey.compareTo(bKey); + } else if (a.key is double) { + return a.key > 4 ? 1 : -1; + } else if (b.key is double) { + return b.key > 4 ? -1 : 1; + } else { + final aKey = a.key as String; + final bKey = b.key as String; + return aKey.compareTo(bKey); + } + })); } static Color getColor(dynamic grade) { diff --git a/lib/gradeComponent/views/chart_view.dart b/lib/gradeComponent/views/chart_view.dart index 290472c3..43523d6d 100644 --- a/lib/gradeComponent/views/chart_view.dart +++ b/lib/gradeComponent/views/chart_view.dart @@ -8,6 +8,7 @@ import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:campus_flutter/theme.dart'; class ChartView extends ConsumerWidget { const ChartView({super.key, required this.studyID, required this.title}); @@ -37,12 +38,15 @@ class ChartView extends ConsumerWidget { ), SfCartesianChart( primaryXAxis: CategoryAxis(), - primaryYAxis: - NumericAxis(minimum: 0, maximum: data.values.reduce(max).toDouble(), interval: 1), + primaryYAxis: NumericAxis( + minimum: 0, + maximum: data.values.reduce(max).toDouble(), + interval: 1), series: , String>>[ ColumnSeries, String>( dataSource: data.entries.toList(), - xValueMapper: (MapEntry data, _) => data.key.toString(), + xValueMapper: (MapEntry data, _) => + data.key.toString(), yValueMapper: (MapEntry data, _) => data.value, pointColorMapper: (MapEntry data, _) => GradeViewModel.getColor(data.key), @@ -52,10 +56,15 @@ class ChartView extends ConsumerWidget { const Divider(), Row( children: [ - Expanded(child: Text("Average Grade:", style: Theme.of(context).textTheme.bodyLarge)), + Expanded( + child: Text(context.localizations.averageGrade, + style: Theme.of(context).textTheme.bodyLarge)), Text( averageGrade.averageGrade.toString(), - style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold), + style: Theme.of(context) + .textTheme + .bodyLarge + ?.copyWith(fontWeight: FontWeight.bold), ) ], ) diff --git a/lib/gradeComponent/views/grade_view.dart b/lib/gradeComponent/views/grade_view.dart index 5e104374..5193713c 100644 --- a/lib/gradeComponent/views/grade_view.dart +++ b/lib/gradeComponent/views/grade_view.dart @@ -23,7 +23,7 @@ class GradeRowAlt extends StatelessWidget { Expanded( child: IconText( iconData: Icons.edit, - label: grade.modusShort, + label: grade.modusShort(context), iconColor: Theme.of(context).primaryColor, textColor: Theme.of(context).colorScheme.secondary)), Expanded( @@ -60,7 +60,9 @@ class GradeRectangle extends StatelessWidget { borderRadius: BorderRadius.circular(4)), child: Center( child: Text( - parsedGrade is double ? parsedGrade.toStringAsFixed(1) : grade.toString(), + parsedGrade is double + ? parsedGrade.toStringAsFixed(1) + : grade.toString(), style: Theme.of(context).textTheme.headlineSmall?.copyWith( color: Colors.white, shadows: [ diff --git a/lib/gradeComponent/views/grades_view.dart b/lib/gradeComponent/views/grades_view.dart index 7d452eaf..31d7aa40 100644 --- a/lib/gradeComponent/views/grades_view.dart +++ b/lib/gradeComponent/views/grades_view.dart @@ -12,6 +12,7 @@ import 'package:campus_flutter/gradeComponent/views/grade_view.dart'; import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:campus_flutter/theme.dart'; class GradesView extends ConsumerStatefulWidget { const GradesView({super.key}); @@ -36,7 +37,7 @@ class _GradesViewState extends ConsumerState { stream: gradeVM.studyProgramGrades, dataBuilder: (context, data) { if (data.isEmpty) { - return const Center(child: Text("no grades found")); + return Center(child: Text(context.localizations.noGradesFound)); } else { final lastFetched = ref.read(gradeViewModel).lastFetched.value; return OrientationBuilder(builder: (context, constraints) { @@ -54,7 +55,7 @@ class _GradesViewState extends ConsumerState { retry: ref.read(gradeViewModel).fetch, ), loadingBuilder: (context) => - const DelayedLoadingIndicator(name: "Grades")); + DelayedLoadingIndicator(name: context.localizations.grades)); } Widget _oneColumnView(Map> data, DateTime? lastFetched) { @@ -132,7 +133,7 @@ class SemesterView extends StatelessWidget { Widget build(BuildContext context) { return Card( child: ExpansionTile( - title: Text(StringParser.toFullSemesterName(semester.key)), + title: Text(StringParser.toFullSemesterName(context, semester.key)), initiallyExpanded: (semester.key == SemesterCalculator.getCurrentSemester() || semester.key == SemesterCalculator.getPriorSemester()), diff --git a/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart b/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart index 7a16da88..46b4f59b 100644 --- a/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart +++ b/lib/homeComponent/contactComponent/views/contact_card_loading_view.dart @@ -10,7 +10,8 @@ class ContactCardLoadingView extends StatelessWidget { return Row( children: [ const CircleAvatar( - backgroundImage: AssetImage('assets/images/placeholders/portrait_placeholder.png'), + backgroundImage: + AssetImage('assets/images/placeholders/portrait_placeholder.png'), radius: 50, ), const Padding(padding: EdgeInsets.only(left: 15)), @@ -23,7 +24,8 @@ class ContactCardLoadingView extends StatelessWidget { style: Theme.of(context).textTheme.headlineSmall, )), const ShimmerView(child: PlaceholderText(text: "go43hum")), - const ShimmerView(child: PlaceholderText(text: "max.mustermann@tum.de")), + const ShimmerView( + child: PlaceholderText(text: "max.mustermann@tum.de")), const ShimmerView(child: PlaceholderText(text: "Informatik B.Sc.")), ], ) diff --git a/lib/homeComponent/contactComponent/views/contact_card_view.dart b/lib/homeComponent/contactComponent/views/contact_card_view.dart index b08d00fb..8a3da4fc 100644 --- a/lib/homeComponent/contactComponent/views/contact_card_view.dart +++ b/lib/homeComponent/contactComponent/views/contact_card_view.dart @@ -6,12 +6,14 @@ import 'package:campus_flutter/personDetailedComponent/viewModel/person_details_ import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:campus_flutter/theme.dart'; class ContactCardView extends ConsumerStatefulWidget { const ContactCardView({super.key}); @override - ConsumerState createState() => _ContactCardViewState(); + ConsumerState createState() => + _ContactCardViewState(); } class _ContactCardViewState extends ConsumerState { @@ -24,6 +26,7 @@ class _ContactCardViewState extends ConsumerState { return contactInfo(snapshot.data); } else { return DelayedLoadingIndicator( + name: context.localizations.personalData, alternativeLoadingIndicator: const ContactCardLoadingView(), delayWidget: Container(), ); @@ -37,7 +40,8 @@ class _ContactCardViewState extends ConsumerState { CircleAvatar( backgroundImage: data?.imageData != null ? Image.memory(base64DecodeImageData(data!.imageData!)).image - : const AssetImage('assets/images/placeholders/portrait_placeholder.png'), + : const AssetImage( + 'assets/images/placeholders/portrait_placeholder.png'), backgroundColor: Colors.white, radius: 50, ), @@ -54,9 +58,11 @@ class _ContactCardViewState extends ConsumerState { Text(ref.watch(profileDetailsViewModel).profile != null ? ref.watch(profileDetailsViewModel).profile?.tumID ?? "go42tum" : "go42tum"), - Text(data != null ? data.email : PersonDetailsViewModel.defaultPersonDetails.email), + Text(data != null + ? data.email + : PersonDetailsViewModel.defaultPersonDetails.email), // TODO: solve with tumCard api? - const Text("coming soon") + Text(context.localizations.comingSoon) ], )) ], diff --git a/lib/homeComponent/contactComponent/views/contact_view.dart b/lib/homeComponent/contactComponent/views/contact_view.dart index 29af5026..7729fafc 100644 --- a/lib/homeComponent/contactComponent/views/contact_view.dart +++ b/lib/homeComponent/contactComponent/views/contact_view.dart @@ -22,7 +22,8 @@ class _ContactScreenState extends ConsumerState { builder: (context, snapshot) { return Column(mainAxisSize: MainAxisSize.min, children: [ ConstrainedBox( - constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.15), + constraints: BoxConstraints( + minHeight: MediaQuery.sizeOf(context).height * 0.15), child: CardWithPadding( child: Center( child: snapshot.hasData diff --git a/lib/homeComponent/contactComponent/views/link_view.dart b/lib/homeComponent/contactComponent/views/link_view.dart index db464163..5020e300 100644 --- a/lib/homeComponent/contactComponent/views/link_view.dart +++ b/lib/homeComponent/contactComponent/views/link_view.dart @@ -14,9 +14,7 @@ class LinkView extends ConsumerWidget { children: [ Expanded( child: SizedBox( - height: MediaQuery - .sizeOf(context) - .height * 0.075, + height: MediaQuery.sizeOf(context).height * 0.075, child: GestureDetector( onTap: () async { if (MediaQuery.orientationOf(context) == @@ -27,9 +25,8 @@ class LinkView extends ConsumerWidget { ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate(NavigationDelegate( - onNavigationRequest: ( - request) => NavigationDecision.navigate - )) + onNavigationRequest: (request) => + NavigationDecision.navigate)) ..loadRequest(Uri.parse("https://moodle.tum.de")); ref .read(homeSplitViewModel) @@ -42,15 +39,13 @@ class LinkView extends ConsumerWidget { right: 5.0, top: 5.0, bottom: 5.0, left: 10.0), child: Center( child: IconText( - iconData: Icons.school_outlined, - label: "Moodle", - iconSize: 24, - )))))), + iconData: Icons.school_outlined, + label: "Moodle", + iconSize: 24, + )))))), Expanded( child: SizedBox( - height: MediaQuery - .sizeOf(context) - .height * 0.075, + height: MediaQuery.sizeOf(context).height * 0.075, child: GestureDetector( onTap: () async { if (MediaQuery.orientationOf(context) == @@ -61,9 +56,8 @@ class LinkView extends ConsumerWidget { ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate(NavigationDelegate( - onNavigationRequest: ( - request) => NavigationDecision.navigate - )) + onNavigationRequest: (request) => + NavigationDecision.navigate)) ..loadRequest(Uri.parse("https://campus.tum.de")); ref .read(homeSplitViewModel) @@ -76,10 +70,10 @@ class LinkView extends ConsumerWidget { right: 10.0, top: 5.0, bottom: 5.0, left: 5.0), child: Center( child: IconText( - iconData: Icons.language_outlined, - label: "TUMOnline", - iconSize: 24, - )))))) + iconData: Icons.language_outlined, + label: "TUMOnline", + iconSize: 24, + )))))) ], ); } diff --git a/lib/homeComponent/contactComponent/views/tuition_view.dart b/lib/homeComponent/contactComponent/views/tuition_view.dart index b1c20398..b9874493 100644 --- a/lib/homeComponent/contactComponent/views/tuition_view.dart +++ b/lib/homeComponent/contactComponent/views/tuition_view.dart @@ -6,6 +6,7 @@ import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:campus_flutter/theme.dart'; class TuitionView extends ConsumerWidget { const TuitionView({super.key}); @@ -29,7 +30,7 @@ class TuitionView extends ConsumerWidget { padding: EdgeInsets.only(right: 10.0), child: Icon(Icons.euro), ), - const Text("Tuition fees"), + Text(context.localizations.tuitionFees), const Spacer(), _tuitionStatus(context, snapshot) ])), @@ -42,55 +43,65 @@ class TuitionView extends ConsumerWidget { context: context, builder: (context) => AlertDialog( title: Text( - "Tuition Fees", + context.localizations.tuitionFees, style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), content: Column(mainAxisSize: MainAxisSize.min, children: [ Text( snapshot.data!.semester, - style: - Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), + style: Theme.of(context) + .textTheme + .bodyLarge + ?.copyWith(fontWeight: FontWeight.w500), ), const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)), - _infoRow("Due Date:", DateFormat("dd.MM.yyyy").format(snapshot.data!.deadline)), + _infoRow(context.localizations.tuitionDueDate, + DateFormat.yMd().format(snapshot.data!.deadline)), _infoRow( - "Open Amount:", + context.localizations.tuitionOpenAmount, NumberFormat.currency(locale: "de_DE", symbol: '€') .format(snapshot.data!.amount)) ]), actions: [ ElevatedButton( - onPressed: () => Navigator.of(context).pop(), child: const Text("OK")) + onPressed: () => Navigator.of(context).pop(), + child: const Text("ok")) ], actionsAlignment: MainAxisAlignment.center, )); } - Widget _tuitionStatus(BuildContext context, AsyncSnapshot snapshot) { + Widget _tuitionStatus( + BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { if (snapshot.data?.amount == 0.0) { - return const IconText( + return IconText( iconData: Icons.check, - label: "Tuition Paid", - style: TextStyle(color: Colors.green), + label: context.localizations.tuitionPaid, + style: const TextStyle(color: Colors.green), leadingIcon: false, ); } else { - final numberFormat = NumberFormat.currency(locale: "de_DE", symbol: "€"); + final numberFormat = + NumberFormat.currency(locale: "de_DE", symbol: "€"); return Text(numberFormat.format(snapshot.data?.amount), style: const TextStyle(color: Colors.red)); } } else { - return const DelayedLoadingIndicator( - name: "Tuition", - alternativeLoadingIndicator: Text("n/a", style: TextStyle(color: Colors.red))); + return DelayedLoadingIndicator( + name: context.localizations.tuition, + alternativeLoadingIndicator: Text( + context.localizations.notAvailableAbbrev, + style: const TextStyle(color: Colors.red))); } } Widget _infoRow(String title, String info) { return Row(children: [ - Expanded(child: Text(title, style: const TextStyle(fontWeight: FontWeight.w500))), + Expanded( + child: + Text(title, style: const TextStyle(fontWeight: FontWeight.w500))), Expanded(child: Text(info)) ]); } diff --git a/lib/homeComponent/contactComponent/views/unauthorized_view.dart b/lib/homeComponent/contactComponent/views/unauthorized_view.dart index b5532d55..c97a975c 100644 --- a/lib/homeComponent/contactComponent/views/unauthorized_view.dart +++ b/lib/homeComponent/contactComponent/views/unauthorized_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:campus_flutter/theme.dart'; class UnauthorizedView extends StatelessWidget { const UnauthorizedView({super.key}); @@ -8,11 +9,13 @@ class UnauthorizedView extends StatelessWidget { return Row( children: [ const CircleAvatar( - backgroundImage: AssetImage('assets/images/placeholders/portrait_placeholder.png'), + backgroundImage: + AssetImage('assets/images/placeholders/portrait_placeholder.png'), radius: 50, ), const Spacer(), - Text("Not Logged In", style: Theme.of(context).textTheme.titleLarge), + Text(context.localizations.notLoggedIn, + style: Theme.of(context).textTheme.titleLarge), const Spacer(flex: 2) ], ); diff --git a/lib/homeComponent/home_screen.dart b/lib/homeComponent/home_screen.dart index a32d9f34..a3d514a0 100644 --- a/lib/homeComponent/home_screen.dart +++ b/lib/homeComponent/home_screen.dart @@ -12,25 +12,20 @@ class HomeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return OrientationBuilder( - builder: (context, orientation) { - if (orientation == Orientation.portrait) { - return _widgetScrollView(); - } else { - return const AnimatedSplitView(); - } - } - ); + return OrientationBuilder(builder: (context, orientation) { + if (orientation == Orientation.portrait) { + return _widgetScrollView(); + } else { + return const AnimatedSplitView(); + } + }); } Widget _widgetScrollView() { return SingleChildScrollView( - controller: scrollController, - child: const Column(children: [ - ContactScreen(), - PaddedDivider(), - WidgetScreen() - ])); + controller: scrollController, + child: const Column( + children: [ContactScreen(), PaddedDivider(), WidgetScreen()])); } } @@ -38,10 +33,12 @@ class AnimatedSplitView extends ConsumerStatefulWidget { const AnimatedSplitView({super.key}); @override - ConsumerState createState() => _AnimatedSplitViewState(); + ConsumerState createState() => + _AnimatedSplitViewState(); } -class _AnimatedSplitViewState extends ConsumerState with SingleTickerProviderStateMixin { +class _AnimatedSplitViewState extends ConsumerState + with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _animation; final ScrollController scrollController = ScrollController(); @@ -49,8 +46,8 @@ class _AnimatedSplitViewState extends ConsumerState with Sing @override void initState() { super.initState(); - _animationController = - AnimationController(duration: const Duration(milliseconds: 200), vsync: this); + _animationController = AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); _animation = IntTween(begin: 0, end: 600).animate(_animationController); _animation.addListener(() => setState(() {})); } @@ -59,9 +56,7 @@ class _AnimatedSplitViewState extends ConsumerState with Sing Widget build(BuildContext context) { final double value = _animation.value / 2; return StreamBuilder( - stream: ref - .read(homeSplitViewModel) - .selectedWidget, + stream: ref.read(homeSplitViewModel).selectedWidget, builder: (context, snapshot) { if (snapshot.hasData) { _animationController.forward(); @@ -73,30 +68,24 @@ class _AnimatedSplitViewState extends ConsumerState with Sing crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 300 - value.toInt(), child: Container()), - Expanded( - flex: 400, - child: _widgetScrollView() - ), + Expanded(flex: 400, child: _widgetScrollView()), Expanded(flex: 300 - value.toInt(), child: Container()), Expanded( flex: _animation.value, child: (snapshot.hasData && _animation.value == 600) ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IconButton( - onPressed: () => - ref - .read(homeSplitViewModel) - .selectedWidget - .add(null), - icon: const Icon(Icons.close) - ), - Expanded(child: snapshot.data!) - ], - ) - : Container() - ) + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + onPressed: () => ref + .read(homeSplitViewModel) + .selectedWidget + .add(null), + icon: const Icon(Icons.close)), + Expanded(child: snapshot.data!) + ], + ) + : Container()) ], ), ); @@ -106,10 +95,7 @@ class _AnimatedSplitViewState extends ConsumerState with Sing Widget _widgetScrollView() { return SingleChildScrollView( controller: scrollController, - child: const Column(children: [ - ContactScreen(), - PaddedDivider(), - WidgetScreen() - ])); + child: const Column( + children: [ContactScreen(), PaddedDivider(), WidgetScreen()])); } -} \ No newline at end of file +} diff --git a/lib/homeComponent/split_view_viewmodel.dart b/lib/homeComponent/split_view_viewmodel.dart index 62f493b1..67112fb9 100644 --- a/lib/homeComponent/split_view_viewmodel.dart +++ b/lib/homeComponent/split_view_viewmodel.dart @@ -3,4 +3,4 @@ import 'package:rxdart/rxdart.dart'; class SplitViewViewModel { BehaviorSubject selectedWidget = BehaviorSubject.seeded(null); -} \ No newline at end of file +} diff --git a/lib/homeComponent/widgetComponent/recommender/location_strategy.dart b/lib/homeComponent/widgetComponent/recommender/location_strategy.dart index 5620ec57..313e4d62 100644 --- a/lib/homeComponent/widgetComponent/recommender/location_strategy.dart +++ b/lib/homeComponent/widgetComponent/recommender/location_strategy.dart @@ -30,8 +30,11 @@ class LocationStrategy implements WidgetRecommenderStrategy { for (var distance in [closeDistance, veryCloseDistance]) { locations.removeWhere((element) => (Geolocator.distanceBetween( - element.latitude, element.longitude, location.latitude, location.longitude) >= - distance)); + element.latitude, + element.longitude, + location.latitude, + location.longitude) >= + distance)); if (locations.isNotEmpty) { priority++; } @@ -41,10 +44,11 @@ class LocationStrategy implements WidgetRecommenderStrategy { final locations = await _getStudyRoomLocations(); for (var distance in [closeDistance, veryCloseDistance]) { - locations.removeWhere((element) => Geolocator.distanceBetween( - element.latitude, element.longitude, location.latitude, location.longitude) >= + locations.removeWhere((element) => + Geolocator.distanceBetween(element.latitude, element.longitude, + location.latitude, location.longitude) >= distance); - + if (locations.isNotEmpty) { priority++; } @@ -54,8 +58,11 @@ class LocationStrategy implements WidgetRecommenderStrategy { List locations = Campus.values.toList(); locations.removeWhere((element) => - Geolocator.distanceBetween(element.location.latitude, element.location.longitude, - location.latitude, location.longitude) >= + Geolocator.distanceBetween( + element.location.latitude, + element.location.longitude, + location.latitude, + location.longitude) >= closeDistance); if (locations.isEmpty) { @@ -76,7 +83,8 @@ class LocationStrategy implements WidgetRecommenderStrategy { try { final cafeterias = await CafeteriasService.fetchCafeterias(false); return cafeterias.$2 - .map((e) => Location(latitude: e.location.latitude, longitude: e.location.longitude)) + .map((e) => Location( + latitude: e.location.latitude, longitude: e.location.longitude)) .toList(); } catch (_) { return []; diff --git a/lib/homeComponent/widgetComponent/recommender/spatial_temporal_strategy.dart b/lib/homeComponent/widgetComponent/recommender/spatial_temporal_strategy.dart index b63944e1..680e2ea7 100644 --- a/lib/homeComponent/widgetComponent/recommender/spatial_temporal_strategy.dart +++ b/lib/homeComponent/widgetComponent/recommender/spatial_temporal_strategy.dart @@ -11,14 +11,17 @@ class SpatialTemporalStrategy implements WidgetRecommenderStrategy { final timeRecommendations = await timeStrategy.getRecommendations(); final locationRecommendations = await locationStrategy.getRecommendations(); - - Map recommendations = Map.fromIterable(HomeWidget.values, key: (homeWidget) => homeWidget, value: (homeWidget) { - if (timeRecommendations[homeWidget] != 0) { - return (timeRecommendations[homeWidget] ?? 0) + (locationRecommendations[homeWidget] ?? 0); - } else { - return 0; - } - }); + + Map recommendations = Map.fromIterable(HomeWidget.values, + key: (homeWidget) => homeWidget, + value: (homeWidget) { + if (timeRecommendations[homeWidget] != 0) { + return (timeRecommendations[homeWidget] ?? 0) + + (locationRecommendations[homeWidget] ?? 0); + } else { + return 0; + } + }); /// remove all where priority is 0 recommendations.removeWhere((key, value) => value <= 1); diff --git a/lib/homeComponent/widgetComponent/recommender/time_strategy.dart b/lib/homeComponent/widgetComponent/recommender/time_strategy.dart index 6b350335..b7037a12 100644 --- a/lib/homeComponent/widgetComponent/recommender/time_strategy.dart +++ b/lib/homeComponent/widgetComponent/recommender/time_strategy.dart @@ -2,10 +2,9 @@ import 'package:campus_flutter/base/enums/home_widget.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/recommender/widget_recommender_strategy.dart'; class TimeStrategy implements WidgetRecommenderStrategy { - @override Future> getRecommendations() { - return Future(() => { for (var e in HomeWidget.values) e : _priority(e) }); + return Future(() => {for (var e in HomeWidget.values) e: _priority(e)}); } int _priority(HomeWidget homeWidget) { diff --git a/lib/homeComponent/widgetComponent/recommender/widget_recommender.dart b/lib/homeComponent/widgetComponent/recommender/widget_recommender.dart index 93cb08da..0287a6d4 100644 --- a/lib/homeComponent/widgetComponent/recommender/widget_recommender.dart +++ b/lib/homeComponent/widgetComponent/recommender/widget_recommender.dart @@ -13,10 +13,12 @@ class WidgetRecommender { Future> fetchRecommendations(WidgetRef ref) async { final recommendations = await _strategy.getRecommendations(); + /// sort by ascending priority - LinkedHashMap sortedRecommendations = LinkedHashMap.fromEntries( - recommendations.entries.toList()..sort((a, b) => b.value.compareTo(a.value)) - ); + LinkedHashMap sortedRecommendations = + LinkedHashMap.fromEntries( + recommendations.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value))); if (ref.read(loginViewModel).credentials.value != Credentials.tumId) { sortedRecommendations.remove(HomeWidget.calendar); @@ -24,4 +26,4 @@ class WidgetRecommender { return sortedRecommendations; } -} \ No newline at end of file +} diff --git a/lib/homeComponent/widgetComponent/recommender/widget_recommender_strategy.dart b/lib/homeComponent/widgetComponent/recommender/widget_recommender_strategy.dart index b0a383b9..f90ca83d 100644 --- a/lib/homeComponent/widgetComponent/recommender/widget_recommender_strategy.dart +++ b/lib/homeComponent/widgetComponent/recommender/widget_recommender_strategy.dart @@ -2,4 +2,4 @@ import 'package:campus_flutter/base/enums/home_widget.dart'; abstract interface class WidgetRecommenderStrategy { Future> getRecommendations(); -} \ No newline at end of file +} diff --git a/lib/homeComponent/widgetComponent/views/widget_frame_view.dart b/lib/homeComponent/widgetComponent/views/widget_frame_view.dart index 3862cfd7..a48ee9c8 100644 --- a/lib/homeComponent/widgetComponent/views/widget_frame_view.dart +++ b/lib/homeComponent/widgetComponent/views/widget_frame_view.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; class WidgetFrameView extends StatelessWidget { - const WidgetFrameView({super.key, this.title, this.subtitle, required this.child}); + const WidgetFrameView( + {super.key, this.title, this.subtitle, required this.child}); final String? title; final Widget? subtitle; @@ -11,12 +12,13 @@ class WidgetFrameView extends StatelessWidget { Widget build(BuildContext context) { return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (title != null) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), - child: Text(title!, - style: Theme.of(context).textTheme.titleMedium, - maxLines: 1, - overflow: TextOverflow.ellipsis)), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), + child: Text(title!, + style: Theme.of(context).textTheme.titleMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis)), if (subtitle != null) subtitle!, child, const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)) diff --git a/lib/homeComponent/widgetComponent/views/widget_screen.dart b/lib/homeComponent/widgetComponent/views/widget_screen.dart index c9cb4a33..958c0288 100644 --- a/lib/homeComponent/widgetComponent/views/widget_screen.dart +++ b/lib/homeComponent/widgetComponent/views/widget_screen.dart @@ -23,7 +23,8 @@ class _WidgetScreenState extends ConsumerState { @override initState() { super.initState(); - recommendations = WidgetRecommender(SpatialTemporalStrategy()).fetchRecommendations(ref); + recommendations = + WidgetRecommender(SpatialTemporalStrategy()).fetchRecommendations(ref); } @override diff --git a/lib/lectureComponent/model/lecture.dart b/lib/lectureComponent/model/lecture.dart index d71277a4..430d13dc 100644 --- a/lib/lectureComponent/model/lecture.dart +++ b/lib/lectureComponent/model/lecture.dart @@ -1,4 +1,6 @@ +import 'package:flutter/cupertino.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:campus_flutter/theme.dart'; part 'lecture.g.dart'; @@ -35,21 +37,22 @@ class Lecture { @JsonKey(name: "vortragende_mitwirkende") final String speaker; - String get eventType { + String eventType(BuildContext context) { switch (eventTypeDefault) { case "Vorlesung": - return "Lecture"; + return context.localizations.lecture; case "Tutorium": + return context.localizations.tutorial; case "Übung": - return "Exercise"; + return context.localizations.exercise; case "Praktikum": - return "Practical course"; + return context.localizations.practicalCourse; case "Seminar": - return "Seminar"; + return context.localizations.seminar; case "Vorlesung mit integrierten Übungen": - return "Lecture with integrated Exercises"; + return context.localizations.lectureWithIntegratedExcercises; default: - return ""; + return eventTypeDefault; } } diff --git a/lib/lectureComponent/model/lecture_details.dart b/lib/lectureComponent/model/lecture_details.dart index 7c49ff6c..cb619537 100644 --- a/lib/lectureComponent/model/lecture_details.dart +++ b/lib/lectureComponent/model/lecture_details.dart @@ -1,4 +1,6 @@ import 'package:campus_flutter/base/helpers/string_parser.dart'; +import 'package:flutter/widgets.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:json_annotation/json_annotation.dart'; part 'lecture_details.g.dart'; @@ -58,18 +60,22 @@ class LectureDetails { @JsonKey(name: "pruef_termine_url") final String? examDateURL; - String get eventType { + String eventType(BuildContext context) { switch (eventTypeDefault) { case "Vorlesung": - return "Lecture"; + return context.localizations.lecture; case "Tutorium": - return "Exercise"; + return context.localizations.tutorial; + case "Übung": + return context.localizations.exercise; case "Praktikum": - return "Practice"; + return context.localizations.practicalCourse; + case "Seminar": + return context.localizations.seminar; case "Vorlesung mit integrierten Übungen": - return "Lecture with integrated Exercises"; + return context.localizations.lectureWithIntegratedExcercises; default: - return ""; + return eventTypeDefault; } } diff --git a/lib/lectureComponent/services/lecture_details_service.dart b/lib/lectureComponent/services/lecture_details_service.dart index a5c4b12e..25a72e03 100644 --- a/lib/lectureComponent/services/lecture_details_service.dart +++ b/lib/lectureComponent/services/lecture_details_service.dart @@ -6,15 +6,19 @@ import 'package:campus_flutter/lectureComponent/model/lecture_details.dart'; import 'package:campus_flutter/providers_get_it.dart'; class LectureDetailsService { - static Future<(DateTime?, LectureDetails)> fetchLectureDetails(String lvNumber, bool forcedRefresh) async { + static Future<(DateTime?, LectureDetails)> fetchLectureDetails( + String lvNumber, bool forcedRefresh) async { MainApi mainApi = getIt(); - final response = await mainApi - .makeRequestWithException( - TumOnlineApi(TumOnlineServiceLectureDetails(lvNr: lvNumber)), - LectureDetailsData.fromJson, - TumOnlineApiException.fromJson, - forcedRefresh); + final response = await mainApi.makeRequestWithException( + TumOnlineApi(TumOnlineServiceLectureDetails(lvNr: lvNumber)), + LectureDetailsData.fromJson, + TumOnlineApiException.fromJson, + forcedRefresh); - return (response.saved, response.data.lectureDetailsAttribute.lectureDetails); + return ( + response.saved, + response.data.lectureDetailsAttribute.lectureDetails + ); } -} \ No newline at end of file +} diff --git a/lib/lectureComponent/viewModels/lecture_details_viewmodel.dart b/lib/lectureComponent/viewModels/lecture_details_viewmodel.dart index 9e9e7222..625dcc32 100644 --- a/lib/lectureComponent/viewModels/lecture_details_viewmodel.dart +++ b/lib/lectureComponent/viewModels/lecture_details_viewmodel.dart @@ -6,7 +6,8 @@ import 'package:campus_flutter/lectureComponent/services/lecture_details_service import 'package:rxdart/rxdart.dart'; class LectureDetailsViewModel implements ViewModel { - BehaviorSubject lectureDetails = BehaviorSubject.seeded(null); + BehaviorSubject lectureDetails = + BehaviorSubject.seeded(null); final BehaviorSubject lastFetched = BehaviorSubject.seeded(null); @@ -20,15 +21,19 @@ class LectureDetailsViewModel implements ViewModel { @override Future fetch(bool forcedRefresh) async { if (event != null) { - LectureDetailsService.fetchLectureDetails(event!.lvNr ?? "", forcedRefresh).then((response) { + LectureDetailsService.fetchLectureDetails( + event!.lvNr ?? "", forcedRefresh) + .then((response) { lastFetched.add(response.$1); lectureDetails.add(response.$2); }, onError: (error) => lectureDetails.addError(error)); } else { - LectureDetailsService.fetchLectureDetails(lecture?.lvNumber ?? "", forcedRefresh).then((response) { + LectureDetailsService.fetchLectureDetails( + lecture?.lvNumber ?? "", forcedRefresh) + .then((response) { lastFetched.add(response.$1); lectureDetails.add(response.$2); }, onError: (error) => lectureDetails.addError(error)); } } -} \ No newline at end of file +} diff --git a/lib/lectureComponent/views/basic_lecture_info_row_view.dart b/lib/lectureComponent/views/basic_lecture_info_row_view.dart index 6171a5d2..6d1e3e51 100644 --- a/lib/lectureComponent/views/basic_lecture_info_row_view.dart +++ b/lib/lectureComponent/views/basic_lecture_info_row_view.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; class BasicLectureInfoRowView extends StatelessWidget { - const BasicLectureInfoRowView({super.key, required this.information, required this.iconData}); + const BasicLectureInfoRowView( + {super.key, required this.information, required this.iconData}); final String information; final IconData iconData; @@ -14,4 +15,4 @@ class BasicLectureInfoRowView extends StatelessWidget { Expanded(child: Text(information)) ]); } -} \ No newline at end of file +} diff --git a/lib/lectureComponent/views/basic_lecture_info_view.dart b/lib/lectureComponent/views/basic_lecture_info_view.dart index dd0c4064..0e81f0ea 100644 --- a/lib/lectureComponent/views/basic_lecture_info_view.dart +++ b/lib/lectureComponent/views/basic_lecture_info_view.dart @@ -26,13 +26,15 @@ class BasicLectureInfoView extends StatelessWidget { if (lectureDetails.speaker != null) ...[ const Divider(), BasicLectureInfoRowView( - information: lectureDetails.speaker!, iconData: Icons.person), + information: lectureDetails.speaker!, iconData: Icons.person), ], if (lectureDetails.firstScheduledDate != null) ...[ const Divider(), - BasicLectureInfoRowView(information: lectureDetails.firstScheduledDate!, iconData: Icons.watch_later) + BasicLectureInfoRowView( + information: lectureDetails.firstScheduledDate!, + iconData: Icons.watch_later) ], ], ); } -} \ No newline at end of file +} diff --git a/lib/lectureComponent/views/detailed_lecture_info_row_view.dart b/lib/lectureComponent/views/detailed_lecture_info_row_view.dart index d9d7cd24..a3164ac6 100644 --- a/lib/lectureComponent/views/detailed_lecture_info_row_view.dart +++ b/lib/lectureComponent/views/detailed_lecture_info_row_view.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; class DetailedLectureInfoRowView extends StatelessWidget { - const DetailedLectureInfoRowView({ - super.key, - required this.title, - required this.information - }); + const DetailedLectureInfoRowView( + {super.key, required this.title, required this.information}); final String title; final String information; @@ -21,4 +18,4 @@ class DetailedLectureInfoRowView extends StatelessWidget { Text(information.replaceAll(r'\\n', "\n")) ]); } -} \ No newline at end of file +} diff --git a/lib/lectureComponent/views/detailed_lecture_info_view.dart b/lib/lectureComponent/views/detailed_lecture_info_view.dart index ebc9faff..334193ea 100644 --- a/lib/lectureComponent/views/detailed_lecture_info_view.dart +++ b/lib/lectureComponent/views/detailed_lecture_info_view.dart @@ -13,20 +13,22 @@ class DetailedLectureInfoView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (lectureDetails.courseContents != null) ...[ - DetailedLectureInfoRowView(title: "Course Contents", + DetailedLectureInfoRowView( + title: "Course Contents", information: lectureDetails.courseContents!) ], if (lectureDetails.courseObjective != null) ...[ const Divider(), - DetailedLectureInfoRowView(title: "Course Objective", + DetailedLectureInfoRowView( + title: "Course Objective", information: lectureDetails.courseObjective!) ], if (lectureDetails.note != null) ...[ const Divider(), - DetailedLectureInfoRowView(title: "Note", - information: lectureDetails.note!) + DetailedLectureInfoRowView( + title: "Note", information: lectureDetails.note!) ], ], ); } -} \ No newline at end of file +} diff --git a/lib/lectureComponent/views/lecture_details_view.dart b/lib/lectureComponent/views/lecture_details_view.dart index b21ebcd5..7daad4f6 100644 --- a/lib/lectureComponent/views/lecture_details_view.dart +++ b/lib/lectureComponent/views/lecture_details_view.dart @@ -8,6 +8,7 @@ import 'package:campus_flutter/lectureComponent/views/basic_lecture_info_row_vie import 'package:campus_flutter/lectureComponent/views/basic_lecture_info_view.dart'; import 'package:campus_flutter/lectureComponent/views/detailed_lecture_info_view.dart'; import 'package:campus_flutter/lectureComponent/views/lecture_links_view.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -19,16 +20,15 @@ class LectureDetailsView extends ConsumerStatefulWidget { final ScrollController? scrollController; @override - ConsumerState createState() => _LectureDetailsViewState(); + ConsumerState createState() => + _LectureDetailsViewState(); } class _LectureDetailsViewState extends ConsumerState { @override Widget build(BuildContext context) { return StreamBuilder( - stream: ref - .watch(lectureDetailsViewModel) - .lectureDetails, + stream: ref.watch(lectureDetailsViewModel).lectureDetails, builder: (context, snapshot) { if (snapshot.hasData) { return lectureDetailsView(snapshot.data!); @@ -36,14 +36,10 @@ class _LectureDetailsViewState extends ConsumerState { return ErrorHandlingView( error: snapshot.error!, errorHandlingViewType: ErrorHandlingViewType.fullScreen, - retry: ref - .read(lectureDetailsViewModel) - .fetch - ); + retry: ref.read(lectureDetailsViewModel).fetch); } else { - return const DelayedLoadingIndicator( - name: "Lecture Details" - ); + return DelayedLoadingIndicator( + name: context.localizations.lectureDetails); } }, ); @@ -66,57 +62,58 @@ class _LectureDetailsViewState extends ConsumerState { Widget lectureDetailsView(LectureDetails lectureDetails) { final lastFetched = ref.read(lectureDetailsViewModel).lastFetched.value; return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(lectureDetails.title, - style: Theme - .of(context) - .textTheme - .headlineSmall, - textAlign: TextAlign.start), - Text(lectureDetails.eventType, textAlign: TextAlign.start), - ])), - const Padding(padding: EdgeInsets.symmetric(vertical: 3.0)), - if (lastFetched != null) LastUpdatedText(lastFetched), - Expanded( - child: Scrollbar( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(lectureDetails.title, + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.start), + Text(lectureDetails.eventType(context), textAlign: TextAlign.start), + ])), + const Padding(padding: EdgeInsets.symmetric(vertical: 3.0)), + if (lastFetched != null) LastUpdatedText(lastFetched), + Expanded( + child: Scrollbar( + controller: widget.scrollController, + child: SingleChildScrollView( controller: widget.scrollController, - child: SingleChildScrollView( - controller: widget.scrollController, - child: SafeArea( - child: Column( - children: _infoCards(lectureDetails), - ))))) - ]); + child: SafeArea( + child: Column( + children: _infoCards(lectureDetails), + ))))) + ]); } List _infoCards(LectureDetails lectureDetails) { return [ if (ref.read(lectureDetailsViewModel).event != null) ...[ - _infoCard(Icons.calendar_month, "This Meeting", - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BasicLectureInfoRowView( - information: ref.read(lectureDetailsViewModel).event!.timeDatePeriod, - iconData: Icons.hourglass_top), - const Divider(), - // TODO: roomfinder - BasicLectureInfoRowView( - information: ref.read(lectureDetailsViewModel).event!.location, - iconData: Icons.location_on) - ], - ) - ) + _infoCard( + Icons.calendar_month, + context.localizations.thisMeeting, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BasicLectureInfoRowView( + information: + ref.read(lectureDetailsViewModel).event!.timeDatePeriod, + iconData: Icons.hourglass_top), + const Divider(), + // TODO: roomfinder + BasicLectureInfoRowView( + information: + ref.read(lectureDetailsViewModel).event!.location, + iconData: Icons.location_on) + ], + )) ], - _infoCard(Icons.info_outline_rounded, "Basic Lecture Information", + _infoCard( + Icons.info_outline_rounded, + context.localizations.basicLectureInformation, BasicLectureInfoView(lectureDetails: lectureDetails)), - _infoCard(Icons.folder, "Detailed Lecture Information", + _infoCard(Icons.folder, context.localizations.detailedLectureInformation, DetailedLectureInfoView(lectureDetails: lectureDetails)), - _infoCard(Icons.link, "Lecture Links", + _infoCard(Icons.link, context.localizations.lectureLinks, LectureLinksView(lectureDetails: lectureDetails)) ]; } diff --git a/lib/lectureComponent/views/lecture_links_view.dart b/lib/lectureComponent/views/lecture_links_view.dart index 4a2e8a37..d6e8bd14 100644 --- a/lib/lectureComponent/views/lecture_links_view.dart +++ b/lib/lectureComponent/views/lecture_links_view.dart @@ -1,5 +1,6 @@ import 'package:campus_flutter/base/helpers/hyperlink_text.dart'; import 'package:campus_flutter/lectureComponent/model/lecture_details.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; class LectureLinksView extends StatelessWidget { @@ -13,17 +14,23 @@ class LectureLinksView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (lectureDetails.curriculumURL != null) ...[ - HyperLinkText(link: lectureDetails.curriculumURL ?? "", label: "Curriculum") + HyperLinkText( + link: lectureDetails.curriculumURL ?? "", + label: context.localizations.lectureCurriculum) ], if (lectureDetails.scheduledDatesURL != null) ...[ const Divider(), - HyperLinkText(link: lectureDetails.scheduledDatesURL ?? "", label: "Dates") + HyperLinkText( + link: lectureDetails.scheduledDatesURL ?? "", + label: context.localizations.scheduledLectureDates) ], if (lectureDetails.examDateURL != null) ...[ const Divider(), - HyperLinkText(link: lectureDetails.examDateURL ?? "", label: "Exam") + HyperLinkText( + link: lectureDetails.examDateURL ?? "", + label: context.localizations.lectureExamDate) ], ], ); } -} \ No newline at end of file +} diff --git a/lib/lectureComponent/views/lectures_view.dart b/lib/lectureComponent/views/lectures_view.dart index fbd2c275..eb130d21 100644 --- a/lib/lectureComponent/views/lectures_view.dart +++ b/lib/lectureComponent/views/lectures_view.dart @@ -11,6 +11,7 @@ import 'package:campus_flutter/lectureComponent/views/lecture_details_view.dart' import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:campus_flutter/theme.dart'; class LecturesView extends ConsumerStatefulWidget { const LecturesView({super.key}); @@ -34,12 +35,16 @@ class _GradeViewState extends ConsumerState { stream: ref.watch(lectureViewModel).lectures, dataBuilder: (context, data) { if (data.isEmpty) { - return const Center(child: Text("no lectures found")); + return Center(child: Text(context.localizations.noLecturesFound)); } else { Future(() { - ref.read(selectedLecture.notifier).state = data.values.first.first; + ref.read(selectedLecture.notifier).state = + data.values.first.first; ref.read(selectedEvent.notifier).state = null; - ref.read(lectureSplitViewModel).selectedWidget.add(const LectureDetailsView()); + ref + .read(lectureSplitViewModel) + .selectedWidget + .add(const LectureDetailsView()); }); final lastFetched = ref.read(lectureViewModel).lastFetched.value; return OrientationBuilder(builder: (context, constraints) { @@ -55,36 +60,45 @@ class _GradeViewState extends ConsumerState { error: error, errorHandlingViewType: ErrorHandlingViewType.fullScreen, retry: ref.read(lectureViewModel).fetch), - loadingBuilder: (context) => const DelayedLoadingIndicator(name: "Lectures")); + loadingBuilder: (context) => + const DelayedLoadingIndicator(name: "Lectures")); } - Widget _twoColumnView(DateTime? lastFetched, Map> data) { + Widget _twoColumnView( + DateTime? lastFetched, Map> data) { return Column( children: [ if (lastFetched != null) LastUpdatedText(lastFetched), Expanded( child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(flex: 2, child: _semesterListView(true, lastFetched, data)), - Expanded(flex: 3, child: StreamBuilder( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, child: _semesterListView(true, lastFetched, data)), + Expanded( + flex: 3, + child: StreamBuilder( stream: ref.read(lectureSplitViewModel).selectedWidget, builder: (context, snapshot) { if (snapshot.hasData) { return snapshot.data!; } else { - return const DelayedLoadingIndicator( - alternativeLoadingIndicator: Center(child: Text("no lecture selected")), + return DelayedLoadingIndicator( + name: context.localizations.lecture, + alternativeLoadingIndicator: Center( + child: Text( + context.localizations.noLecturesSelected)), ); } })) - ], - )) + ], + )) ], ); } - Widget _semesterListView(bool landScape, DateTime? lastFetched, Map> data) { + Widget _semesterListView( + bool landScape, DateTime? lastFetched, Map> data) { return RefreshIndicator( onRefresh: () => ref.read(lectureViewModel).fetch(true), child: Scrollbar( @@ -92,8 +106,10 @@ class _GradeViewState extends ConsumerState { child: SingleChildScrollView( controller: scrollController, child: Column(children: [ - if (lastFetched != null && !landScape) LastUpdatedText(lastFetched), - for (var semester in data.entries) SemesterView(semester: semester), + if (lastFetched != null && !landScape) + LastUpdatedText(lastFetched), + for (var semester in data.entries) + SemesterView(semester: semester), ]))), ); } @@ -108,8 +124,9 @@ class SemesterView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Card( child: ExpansionTile( - title: Text(StringParser.toFullSemesterName(semester.key)), - initiallyExpanded: semester.key == SemesterCalculator.getCurrentSemester(), + title: Text(StringParser.toFullSemesterName(context, semester.key)), + initiallyExpanded: + semester.key == SemesterCalculator.getCurrentSemester(), children: [ for (var index = 0; index < semester.value.length; index++) Column(children: [ @@ -124,7 +141,7 @@ class SemesterView extends ConsumerWidget { Expanded( child: IconText( iconData: Icons.edit, - label: semester.value[index].eventType, + label: semester.value[index].eventType(context), iconColor: Theme.of(context).primaryColor, textColor: Theme.of(context).colorScheme.secondary, multipleLines: true, @@ -134,7 +151,8 @@ class SemesterView extends ConsumerWidget { iconData: Icons.access_time, label: semester.value[index].sws, iconColor: Theme.of(context).primaryColor, - textColor: Theme.of(context).colorScheme.secondary)), + textColor: + Theme.of(context).colorScheme.secondary)), ]), const Padding(padding: EdgeInsets.symmetric(vertical: 2.0)), IconText( @@ -147,7 +165,8 @@ class SemesterView extends ConsumerWidget { ], ), onTap: () { - ref.read(selectedLecture.notifier).state = semester.value[index]; + ref.read(selectedLecture.notifier).state = + semester.value[index]; ref.read(selectedEvent.notifier).state = null; if (MediaQuery.orientationOf(context) == Orientation.portrait) { Navigator.push( @@ -157,11 +176,16 @@ class SemesterView extends ConsumerWidget { appBar: AppBar(leading: const BackButton()), body: const LectureDetailsView()))); } else { - ref.read(lectureSplitViewModel).selectedWidget.add(const LectureDetailsView()); + ref + .read(lectureSplitViewModel) + .selectedWidget + .add(const LectureDetailsView()); } }, ), - (index != semester.value.length - 1 ? const PaddedDivider() : const SizedBox.shrink()) + (index != semester.value.length - 1 + ? const PaddedDivider() + : const SizedBox.shrink()) ]) ], )); diff --git a/lib/loginComponent/model/confirm.dart b/lib/loginComponent/model/confirm.dart index 0de8f4dc..97180d5f 100644 --- a/lib/loginComponent/model/confirm.dart +++ b/lib/loginComponent/model/confirm.dart @@ -9,7 +9,8 @@ class Confirm { Confirm({required this.confirmed}); - factory Confirm.fromJson(Map json) => _$ConfirmFromJson(json); + factory Confirm.fromJson(Map json) => + _$ConfirmFromJson(json); Map toJson() => _$ConfirmToJson(this); -} \ No newline at end of file +} diff --git a/lib/loginComponent/model/token.dart b/lib/loginComponent/model/token.dart index 6896eb28..8da13837 100644 --- a/lib/loginComponent/model/token.dart +++ b/lib/loginComponent/model/token.dart @@ -12,4 +12,4 @@ class Token { factory Token.fromJson(Map json) => _$TokenFromJson(json); Map toJson() => _$TokenToJson(this); -} \ No newline at end of file +} diff --git a/lib/loginComponent/services/login_service.dart b/lib/loginComponent/services/login_service.dart index 7d6f55ca..9c408cd5 100644 --- a/lib/loginComponent/services/login_service.dart +++ b/lib/loginComponent/services/login_service.dart @@ -15,24 +15,24 @@ class LoginService { final response = await mainApi .makeRequestWithException( TumOnlineApi(TumOnlineServiceTokenRequest( - tumId: name, deviceName: "TCA - ${kIsWeb ? "Web App" : io.Platform.localHostname}")), + tumId: name, + deviceName: + "TCA - ${kIsWeb ? "Web App" : io.Platform.localHostname}")), Token.fromJson, TumOnlineApiException.fromJson, forcedRefresh); return response.data; } - + static Future confirmToken(bool forcedRefresh) async { MainApi mainApi = getIt(); final response = await mainApi - .makeRequestWithException( - TumOnlineApi(TumOnlineServiceTokenConfirmation()), - Confirm.fromJson, - TumOnlineApiException.fromJson, - forcedRefresh); + .makeRequestWithException( + TumOnlineApi(TumOnlineServiceTokenConfirmation()), + Confirm.fromJson, + TumOnlineApiException.fromJson, + forcedRefresh); return response.data; } -} \ No newline at end of file +} diff --git a/lib/loginComponent/viewModels/login_viewmodel.dart b/lib/loginComponent/viewModels/login_viewmodel.dart index 4da73842..9341f833 100644 --- a/lib/loginComponent/viewModels/login_viewmodel.dart +++ b/lib/loginComponent/viewModels/login_viewmodel.dart @@ -94,14 +94,13 @@ class LoginViewModel { } Future requestLogin() async { - return LoginService.requestNewToken( - true, - "${textEditingController1.text}${textEditingController2.text}${textEditingController3.text}") + return LoginService.requestNewToken(true, + "${textEditingController1.text}${textEditingController2.text}${textEditingController3.text}") .then((value) { - final token = value.content; - _storage.write(key: "token", value: token); - Api.tumToken = token; - }); + final token = value.content; + _storage.write(key: "token", value: token); + Api.tumToken = token; + }); } Future confirmLogin() async { diff --git a/lib/loginComponent/views/confirm_view.dart b/lib/loginComponent/views/confirm_view.dart index 566935ff..0b565a1e 100644 --- a/lib/loginComponent/views/confirm_view.dart +++ b/lib/loginComponent/views/confirm_view.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:video_player/video_player.dart'; +import 'package:campus_flutter/theme.dart'; class ConfirmView extends ConsumerStatefulWidget { const ConfirmView({super.key}); @@ -77,15 +78,16 @@ class _ConfirmViewState extends ConsumerState { @override Widget build(BuildContext context) { - final backgroundColor = MediaQuery.platformBrightnessOf(context) == Brightness.dark - ? Theme.of(context).canvasColor - : Colors.white; + final backgroundColor = + MediaQuery.platformBrightnessOf(context) == Brightness.dark + ? Theme.of(context).canvasColor + : Colors.white; return Scaffold( backgroundColor: backgroundColor, appBar: AppBar( leading: const BackButton(), backgroundColor: backgroundColor, - title: const Text("Check Token")), + title: Text(context.localizations.checkToken)), body: Column(children: [ Text(texts[currentText], textAlign: TextAlign.center), const Spacer(), @@ -96,40 +98,45 @@ class _ConfirmViewState extends ConsumerState { const Spacer(flex: 2), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( - onPressed: () => UrlLauncher.urlString("https://campus.tum.de", ref), + onPressed: () => + UrlLauncher.urlString("https://campus.tum.de", ref), child: const IconText( iconData: Icons.language, label: "TUMOnline", style: TextStyle(color: Colors.white))), ElevatedButton( onPressed: () { - ref.read(loginViewModel).confirmLogin().then( - (value) { - if (value.confirmed) { - Navigator - .of(context) - .push(MaterialPageRoute( - builder: (context) => const PermissionCheckView())); - } else { - throw TumOnlineApiException(tumOnlineApiExceptionType: TumOnlineApiExceptionTokenNotConfirmed()); - } - }, - onError: (error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - duration: const Duration(seconds: 10), - content: ErrorHandlingView(error: error, errorHandlingViewType: ErrorHandlingViewType.textOnly, titleColor: Colors.white,))); - }).catchError((error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - duration: const Duration(seconds: 10), - content: ErrorHandlingView(error: error, errorHandlingViewType: ErrorHandlingViewType.textOnly, titleColor: Colors.white))); + ref.read(loginViewModel).confirmLogin().then((value) { + if (value.confirmed) { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const PermissionCheckView())); + } else { + throw TumOnlineApiException( + tumOnlineApiExceptionType: + TumOnlineApiExceptionTokenNotConfirmed()); + } + }, onError: (error) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: const Duration(seconds: 10), + content: ErrorHandlingView( + error: error, + errorHandlingViewType: ErrorHandlingViewType.textOnly, + titleColor: Colors.white, + ))); + }).catchError((error) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: const Duration(seconds: 10), + content: ErrorHandlingView( + error: error, + errorHandlingViewType: + ErrorHandlingViewType.textOnly, + titleColor: Colors.white))); }); }, - child: const IconText( + child: IconText( iconData: Icons.arrow_forward, - label: "Check Token", - style: TextStyle(color: Colors.white), + label: context.localizations.checkToken, + style: const TextStyle(color: Colors.white), leadingIcon: false, )), ]), @@ -142,13 +149,16 @@ class _ConfirmViewState extends ConsumerState { final osVersion = Platform.operatingSystemVersion; String email = Uri.encodeComponent("app@tum.de"); - String subject = Uri.encodeComponent("[$operatingSystem - Token]"); - String body = Uri.encodeComponent("Hello, I have an issue activating the token of Campus Online in the TCA version ${info.version} with build number ${info.buildNumber} on $osVersion. Please describe the problem in more detail:\n"); //output: Hello%20Flutter - Uri emailUri = Uri.parse("mailto:$email?subject=$subject&body=$body"); + String subject = + Uri.encodeComponent("[$operatingSystem - Token]"); + String body = Uri.encodeComponent( + "Hello, I have an issue activating the token of Campus Online in the TCA version ${info.version} with build number ${info.buildNumber} on $osVersion. Please describe the problem in more detail:\n"); + Uri emailUri = + Uri.parse("mailto:$email?subject=$subject&body=$body"); UrlLauncher.url(emailUri, ref); }, - child: Text("Contact Support", + child: Text(context.localizations.contactSupport, style: TextStyle(color: Theme.of(context).primaryColor)))), const Spacer(flex: 2) diff --git a/lib/loginComponent/views/login_view.dart b/lib/loginComponent/views/login_view.dart index ce9188bb..615e5993 100644 --- a/lib/loginComponent/views/login_view.dart +++ b/lib/loginComponent/views/login_view.dart @@ -1,9 +1,11 @@ import 'package:campus_flutter/base/views/error_handling_view.dart'; import 'package:campus_flutter/providers_get_it.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:campus_flutter/loginComponent/views/confirm_view.dart'; +import 'package:campus_flutter/theme.dart'; class LoginView extends ConsumerStatefulWidget { const LoginView({super.key}); @@ -13,7 +15,6 @@ class LoginView extends ConsumerStatefulWidget { } class _LoginViewState extends ConsumerState { - @override void initState() { ref.read(loginViewModel).clearTextFields(); @@ -22,76 +23,91 @@ class _LoginViewState extends ConsumerState { @override Widget build(BuildContext context) { + final orientation = MediaQuery.of(context).orientation; return Scaffold( - backgroundColor: MediaQuery.platformBrightnessOf(context) == Brightness.dark - ? Theme.of(context).canvasColor - : Colors.white, - body: SafeArea( - child: OrientationBuilder( - builder: (context, orientation) { - if (orientation == Orientation.landscape) { - return Row( - children: [ - Expanded( - flex: 3, - child: Column( - children: [ - const Spacer(), - const Image( - image: AssetImage("assets/images/logos/tum-logo-blue.png"), - height: 60, - ), - const Padding(padding: EdgeInsets.symmetric(vertical: 10.0)), - Text("Welcome to the TUM Campus App", - style: Theme.of(context).textTheme.titleLarge), - const Padding(padding: EdgeInsets.symmetric(vertical: 10.0)), - Text("Enter your TUM ID to get started", - style: Theme.of(context).textTheme.titleMedium), - const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)), - _tumIdTextFields(context, ref), - const Padding(padding: EdgeInsets.symmetric(vertical: 10.0)), - _loginButton(context, ref), - const Padding(padding: EdgeInsets.symmetric(vertical: 10.0)), - _skipLoginButton(context, ref), - const Spacer(), - ], - ) - ), - Expanded( - flex: 2, - child: _towerImage() - ) - ], - ); - } else { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - const Image( - image: AssetImage("assets/images/logos/tum-logo-blue.png"), - height: 60, - ), - const Padding(padding: EdgeInsets.symmetric(vertical: 10.0)), - Text("Welcome to the TUM Campus App", - style: Theme.of(context).textTheme.titleLarge), - const Spacer(), - Text("Enter your TUM ID to get started", - style: Theme.of(context).textTheme.titleMedium), - const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)), - _tumIdTextFields(context, ref), - const Padding(padding: EdgeInsets.symmetric(vertical: 10.0)), - _loginButton(context, ref), - const Padding(padding: EdgeInsets.symmetric(vertical: 10.0)), - _skipLoginButton(context, ref), - const Spacer(), - _towerImage(), - const Spacer() - ], - ); - } + backgroundColor: + MediaQuery.platformBrightnessOf(context) == Brightness.dark + ? Theme.of(context).canvasColor + : Colors.white, + resizeToAvoidBottomInset: + orientation != Orientation.portrait && !kIsWeb, + body: SafeArea( + maintainBottomViewPadding: true, + child: OrientationBuilder(builder: (context, orientation) { + if (orientation == Orientation.landscape) { + return Row( + children: [ + Expanded( + flex: 3, + child: Column( + children: [ + const Spacer(), + const Image( + image: AssetImage( + "assets/images/logos/tum-logo-blue.png"), + height: 60, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0)), + Text( + context.localizations.welcomeToTheApp, + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0)), + Text( + context.localizations.enterYourIDToStart, + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 5.0)), + _tumIdTextFields(context, ref), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0)), + _loginButton(context, ref), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0)), + _skipLoginButton(context, ref), + const Spacer(), + ], + )), + Expanded(flex: 2, child: _towerImage()) + ], + ); + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + const Image( + image: + AssetImage("assets/images/logos/tum-logo-blue.png"), + height: 60, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0)), + Text(context.localizations.welcomeToTheApp, + style: Theme.of(context).textTheme.titleLarge), + const Spacer(), + Text(context.localizations.enterYourIDToStart, + style: Theme.of(context).textTheme.titleMedium), + const Padding(padding: EdgeInsets.symmetric(vertical: 5.0)), + _tumIdTextFields(context, ref), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0)), + _loginButton(context, ref), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0)), + _skipLoginButton(context, ref), + const Spacer(), + _towerImage(), + const Spacer() + ], + ); } - ))); + }))); } Widget _tumIdTextFields(BuildContext context, WidgetRef ref) { @@ -177,7 +193,7 @@ class _LoginViewState extends ConsumerState { }); } : null, - child: Text("Log in", + child: Text(context.localizations.login, style: Theme.of(context) .textTheme .titleLarge @@ -194,7 +210,7 @@ class _LoginViewState extends ConsumerState { Widget _skipLoginButton(BuildContext context, WidgetRef ref) { return GestureDetector( onTap: () => ref.read(loginViewModel).skip(), - child: Text("Continue without TUM ID", + child: Text(context.localizations.continueWithoutID, style: Theme.of(context) .textTheme .bodySmall diff --git a/lib/loginComponent/views/permission_check_view.dart b/lib/loginComponent/views/permission_check_view.dart index d2b6bd92..c35bd1be 100644 --- a/lib/loginComponent/views/permission_check_view.dart +++ b/lib/loginComponent/views/permission_check_view.dart @@ -4,6 +4,7 @@ import 'package:campus_flutter/lectureComponent/services/lecture_service.dart'; import 'package:campus_flutter/profileComponent/services/profile_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:campus_flutter/theme.dart'; class PermissionCheckView extends ConsumerStatefulWidget { const PermissionCheckView({super.key, this.isSettingsView = false}); @@ -28,11 +29,11 @@ class _PermissionCheckViewState extends ConsumerState { fetchCalendar = CalendarService.fetchCalendar(true); fetchLecture = LectureService.fetchLecture(true); fetchGrades = GradeService.fetchGrades(true); - fetchProfile = ProfileService.fetchProfile(true).then((value) => - fetchTuition = ProfileService.fetchTuition( + fetchProfile = ProfileService.fetchProfile(true).then( + (value) => fetchTuition = ProfileService.fetchTuition( true, value.$2.personGroup ?? "", value.$2.id ?? ""), - onError: (error) - => fetchTuition = ProfileService.fetchTuition(true, "", "id")); + onError: (error) => + fetchTuition = ProfileService.fetchTuition(true, "", "id")); super.initState(); } @@ -42,7 +43,7 @@ class _PermissionCheckViewState extends ConsumerState { backgroundColor: Theme.of(context).colorScheme.surface, appBar: AppBar( leading: const BackButton(), - title: const Text("Check Permissions"), + title: Text(context.localizations.checkPermissions), ), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), @@ -50,20 +51,21 @@ class _PermissionCheckViewState extends ConsumerState { Expanded( flex: 0, child: Text( - "You can change your permissions on TUMOnline", + context.localizations.permissionChangePossibleInTUMonline, style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, )), const Spacer(), - _permissionCheck("Calendar", fetchCalendar), + _permissionCheck(context.localizations.calendar, fetchCalendar), const Spacer(), - _permissionCheck("Lectures", fetchLecture), + _permissionCheck(context.localizations.lectures, fetchLecture), const Spacer(), - _permissionCheck("Grades", fetchGrades), + _permissionCheck(context.localizations.grades, fetchGrades), const Spacer(), - _permissionCheck("Tuition", fetchTuition), + _permissionCheck(context.localizations.tuition, fetchTuition), const Spacer(), - _permissionCheck("Identification", fetchProfile), + _permissionCheck( + context.localizations.identification, fetchProfile), const Spacer(flex: 3), Visibility( visible: confirmedPermissions.keys.length == 5, @@ -75,10 +77,11 @@ class _PermissionCheckViewState extends ConsumerState { if (widget.isSettingsView) { Navigator.of(context).pop(); } else { - Navigator.of(context).popUntil((route) => route.isFirst); + Navigator.of(context) + .popUntil((route) => route.isFirst); } }, - child: const Text("Done"))), + child: Text(context.localizations.done))), const Spacer(flex: 3) ]), )); diff --git a/lib/main.dart b/lib/main.dart index 9634681b..3cf82161 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); @@ -27,16 +28,19 @@ main() async { runApp(const ProviderScope(child: CampusApp())); } -class CampusApp extends StatelessWidget { +class CampusApp extends ConsumerWidget { const CampusApp({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return MaterialApp( title: "TUM Campus App", debugShowCheckedModeBanner: false, theme: lightTheme(context), darkTheme: darkTheme(context), + locale: ref.watch(locale), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, routes: { confirm: (context) => const ConfirmView(), }, diff --git a/lib/movieComponent/model/movie.dart b/lib/movieComponent/model/movie.dart index ecf06ac3..ff22a7ba 100644 --- a/lib/movieComponent/model/movie.dart +++ b/lib/movieComponent/model/movie.dart @@ -22,22 +22,22 @@ class Movie { final Uri? trailer; final Uri link; - Movie({ - required this.id, - required this.date, - required this.created, - required String title, - required this.year, - this.runTime, - required this.genre, - required this.director, - required this.actors, - this.rating, - required this.description, - required this.cover, - required this.trailer, - required this.link - }) : title = title.split(":")[1].trim(); + Movie( + {required this.id, + required this.date, + required this.created, + required String title, + required this.year, + this.runTime, + required this.genre, + required this.director, + required this.actors, + this.rating, + required this.description, + required this.cover, + required this.trailer, + required this.link}) + : title = title.split(":")[1].trim(); factory Movie.fromJson(Map json) => _$MovieFromJson(json); @@ -51,7 +51,8 @@ class MoviesData { MoviesData({required this.movies}); - factory MoviesData.fromJson(Map json) => _$MoviesDataFromJson(json); + factory MoviesData.fromJson(Map json) => + _$MoviesDataFromJson(json); Map toJson() => _$MoviesDataToJson(this); -} \ No newline at end of file +} diff --git a/lib/movieComponent/service/movie_service.dart b/lib/movieComponent/service/movie_service.dart index b4dee0f0..2168e16b 100644 --- a/lib/movieComponent/service/movie_service.dart +++ b/lib/movieComponent/service/movie_service.dart @@ -5,14 +5,14 @@ import 'package:campus_flutter/movieComponent/model/movie.dart'; import 'package:campus_flutter/providers_get_it.dart'; class MovieService { - static Future<(DateTime?, List)> fetchMovies(bool forcedRefresh) async { + static Future<(DateTime?, List)> fetchMovies( + bool forcedRefresh) async { MainApi mainApi = getIt(); final response = await mainApi.makeRequest( TumCabeApi(tumCabeService: TumCabeServiceMovie()), MoviesData.fromJson, - forcedRefresh - ); + forcedRefresh); return (response.saved, response.data.movies); } -} \ No newline at end of file +} diff --git a/lib/movieComponent/viewModel/movies_viewmodel.dart b/lib/movieComponent/viewModel/movies_viewmodel.dart index ef550b69..c871dc41 100644 --- a/lib/movieComponent/viewModel/movies_viewmodel.dart +++ b/lib/movieComponent/viewModel/movies_viewmodel.dart @@ -10,8 +10,9 @@ class MovieViewModel implements ViewModel { @override Future fetch(bool forcedRefresh) async { - MovieService.fetchMovies(forcedRefresh) - .then((response) => _sortMovies(response), onError: (error) => movies.addError(error)); + MovieService.fetchMovies(forcedRefresh).then( + (response) => _sortMovies(response), + onError: (error) => movies.addError(error)); } _sortMovies((DateTime?, List) movies) { diff --git a/lib/movieComponent/views/homeWidget/movies_widget_view.dart b/lib/movieComponent/views/homeWidget/movies_widget_view.dart index cfb974d8..781297b8 100644 --- a/lib/movieComponent/views/homeWidget/movies_widget_view.dart +++ b/lib/movieComponent/views/homeWidget/movies_widget_view.dart @@ -8,6 +8,7 @@ import 'package:campus_flutter/movieComponent/views/homeWidget/movie_card_view.d import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:campus_flutter/theme.dart'; class MoviesHomeWidget extends ConsumerStatefulWidget { const MoviesHomeWidget({super.key}); @@ -35,14 +36,15 @@ class _MoviesHomeWidgetState extends ConsumerState { return Card( child: SizedBox( height: MediaQuery.of(context).size.height * 0.34, - child: const Text("no movies found"))); + child: Text(context.localizations.noMoviesFound))); } else { return LayoutBuilder(builder: (context, constraints) { return HorizontalSlider( data: data, height: MediaQuery.of(context).size.height * 0.34, child: (data) { - return MovieCardView(movie: data, width: constraints.maxWidth * 0.4); + return MovieCardView( + movie: data, width: constraints.maxWidth * 0.4); }); }); } @@ -52,12 +54,12 @@ class _MoviesHomeWidgetState extends ConsumerState { height: MediaQuery.of(context).size.height * 0.34, child: ErrorHandlingView( error: error, - errorHandlingViewType: - ErrorHandlingViewType.textOnly, + errorHandlingViewType: ErrorHandlingViewType.textOnly, retry: ref.read(movieViewModel).fetch))), loadingBuilder: (context) => Card( child: SizedBox( height: MediaQuery.of(context).size.height * 0.34, - child: const DelayedLoadingIndicator(name: "Movies"))))); + child: DelayedLoadingIndicator( + name: context.localizations.movies))))); } } diff --git a/lib/navigation.dart b/lib/navigation.dart index 7fd2d265..ed50b468 100644 --- a/lib/navigation.dart +++ b/lib/navigation.dart @@ -3,6 +3,7 @@ import 'package:campus_flutter/gradeComponent/views/grades_view.dart'; import 'package:campus_flutter/homeComponent/home_screen.dart'; import 'package:campus_flutter/lectureComponent/views/lectures_view.dart'; import 'package:campus_flutter/providers_get_it.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:campus_flutter/settingsComponent/views/settings_view.dart'; import 'package:flutter/material.dart'; @@ -36,47 +37,33 @@ class _NavigationState extends ConsumerState { centerTitle: true, leadingWidth: 80, leading: (kIsWeb && isLandScape) - ? Padding(padding: const EdgeInsets.all(15), - child: Image.asset('assets/images/logos/tum-logo-blue.png', - fit: BoxFit.scaleDown)) + ? Padding( + padding: const EdgeInsets.all(15), + child: Image.asset('assets/images/logos/tum-logo-blue.png', + fit: BoxFit.scaleDown)) : IconButton(onPressed: () {}, icon: const Icon(Icons.search)), title: (() { switch (currentPageIndex) { case 0: if (kIsWeb && isLandScape) { - return Text("Home", - style: Theme - .of(context) - .textTheme - .titleLarge); + return Text(context.localizations.home, + style: Theme.of(context).textTheme.titleLarge); } else { return Image.asset('assets/images/logos/tum-logo-blue.png', fit: BoxFit.cover, height: 20); } case 1: - return Text("Grades", - style: Theme - .of(context) - .textTheme - .titleLarge); + return Text(context.localizations.grades, + style: Theme.of(context).textTheme.titleLarge); case 2: - return Text("Lectures", - style: Theme - .of(context) - .textTheme - .titleLarge); + return Text(context.localizations.lectures, + style: Theme.of(context).textTheme.titleLarge); case 3: - return Text("Calendar", - style: Theme - .of(context) - .textTheme - .titleLarge); + return Text(context.localizations.calendar, + style: Theme.of(context).textTheme.titleLarge); case 4: - return Text("Places", - style: Theme - .of(context) - .textTheme - .titleLarge); + return Text(context.localizations.places, + style: Theme.of(context).textTheme.titleLarge); default: return Image.asset('assets/images/logos/tum-logo-blue.png', fit: BoxFit.contain, height: 20); @@ -93,12 +80,11 @@ class _NavigationState extends ConsumerState { icon: const Icon(Icons.settings)), ], ), //: null, - bottomNavigationBar: (kIsWeb && isLandScape) - ? null - : _bottomNavigationBar(), + bottomNavigationBar: + (kIsWeb && isLandScape) ? null : _bottomNavigationBar(), body: SafeArea( child: (kIsWeb && isLandScape) - ? _webNavigationRail() + ? _webNavigationRail(context) : _navigationBody()), ); }); @@ -110,8 +96,8 @@ class _NavigationState extends ConsumerState { const GradesView(), const LecturesView(), const CalendarsView(), - // TODO: replace with places widget - const Text("Coming Soon") + Text( + context.localizations.comingSoon), // TODO: replace with places widget ][currentPageIndex]; } @@ -135,6 +121,7 @@ class _NavigationState extends ConsumerState { } }); }, + /// Platform is not supported on web height: !kIsWeb ? Platform.isIOS @@ -142,37 +129,37 @@ class _NavigationState extends ConsumerState { : null : null, selectedIndex: currentPageIndex, - destinations: const [ + destinations: [ NavigationDestination( - icon: Icon(Icons.house_outlined), - selectedIcon: Icon(Icons.house), - label: 'Home', + icon: const Icon(Icons.house_outlined), + selectedIcon: const Icon(Icons.house), + label: context.localizations.home, ), NavigationDestination( - icon: Icon(Icons.school_outlined), - selectedIcon: Icon(Icons.school), - label: 'Grades', + icon: const Icon(Icons.school_outlined), + selectedIcon: const Icon(Icons.school), + label: context.localizations.grades, ), NavigationDestination( - icon: Icon(Icons.class_outlined), - selectedIcon: Icon(Icons.class_), - label: 'Lectures', + icon: const Icon(Icons.class_outlined), + selectedIcon: const Icon(Icons.class_), + label: context.localizations.lectures, ), NavigationDestination( - icon: Icon(Icons.calendar_month_outlined), - selectedIcon: Icon(Icons.calendar_month), - label: 'Calendar', + icon: const Icon(Icons.calendar_month_outlined), + selectedIcon: const Icon(Icons.calendar_month), + label: context.localizations.calendar, ), NavigationDestination( - icon: Icon(Icons.place_outlined), - selectedIcon: Icon(Icons.place), - label: 'Places', + icon: const Icon(Icons.place_outlined), + selectedIcon: const Icon(Icons.place), + label: context.localizations.places, ), ], )); } - Widget _webNavigationRail() { + Widget _webNavigationRail(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -186,31 +173,31 @@ class _NavigationState extends ConsumerState { }); }, labelType: NavigationRailLabelType.all, - destinations: const [ + destinations: [ NavigationRailDestination( - icon: Icon(Icons.home_outlined), - selectedIcon: Icon(Icons.home), - label: Text('Home'), + icon: const Icon(Icons.home_outlined), + selectedIcon: const Icon(Icons.home), + label: Text(context.localizations.home), ), NavigationRailDestination( - icon: Icon(Icons.school_outlined), - selectedIcon: Icon(Icons.school), - label: Text('Grades'), + icon: const Icon(Icons.school_outlined), + selectedIcon: const Icon(Icons.school), + label: Text(context.localizations.grades), ), NavigationRailDestination( - icon: Icon(Icons.class_outlined), - selectedIcon: Icon(Icons.class_), - label: Text('Lectures'), + icon: const Icon(Icons.class_outlined), + selectedIcon: const Icon(Icons.class_), + label: Text(context.localizations.lectures), ), NavigationRailDestination( - icon: Icon(Icons.calendar_month_outlined), - selectedIcon: Icon(Icons.calendar_month), - label: Text('Calendar'), + icon: const Icon(Icons.calendar_month_outlined), + selectedIcon: const Icon(Icons.calendar_month), + label: Text(context.localizations.calendar), ), NavigationRailDestination( - icon: Icon(Icons.place_outlined), - selectedIcon: Icon(Icons.place), - label: Text('Places'), + icon: const Icon(Icons.place_outlined), + selectedIcon: const Icon(Icons.place), + label: Text(context.localizations.places), ), ], ), diff --git a/lib/newsComponent/model/news.dart b/lib/newsComponent/model/news.dart index 9f9b3013..5f1b5ace 100644 --- a/lib/newsComponent/model/news.dart +++ b/lib/newsComponent/model/news.dart @@ -14,15 +14,14 @@ class News { final Uri link; final Uri image; - News({ - required this.id, - required this.source, - required this.date, - required this.created, - required this.title, - required this.link, - required this.image - }); + News( + {required this.id, + required this.source, + required this.date, + required this.created, + required this.title, + required this.link, + required this.image}); factory News.fromJson(Map json) => _$NewsFromJson(json); @@ -36,7 +35,8 @@ class NewsData { NewsData({required this.news}); - factory NewsData.fromJson(Map json) => _$NewsDataFromJson(json); + factory NewsData.fromJson(Map json) => + _$NewsDataFromJson(json); Map toJson() => _$NewsDataToJson(this); -} \ No newline at end of file +} diff --git a/lib/newsComponent/model/news_source.dart b/lib/newsComponent/model/news_source.dart index 61a85bc7..b80038fd 100644 --- a/lib/newsComponent/model/news_source.dart +++ b/lib/newsComponent/model/news_source.dart @@ -12,13 +12,10 @@ class NewsSource { final Uri? icon; final List news = []; - NewsSource({ - required this.id, - this.title, - this.icon - }); + NewsSource({required this.id, this.title, this.icon}); - factory NewsSource.fromJson(Map json) => _$NewsSourceFromJson(json); + factory NewsSource.fromJson(Map json) => + _$NewsSourceFromJson(json); Map toJson() => _$NewsSourceToJson(this); } @@ -30,7 +27,8 @@ class NewsSources { NewsSources({required this.newsSources}); - factory NewsSources.fromJson(Map json) => _$NewsSourcesFromJson(json); + factory NewsSources.fromJson(Map json) => + _$NewsSourcesFromJson(json); Map toJson() => _$NewsSourcesToJson(this); -} \ No newline at end of file +} diff --git a/lib/newsComponent/service/news_service.dart b/lib/newsComponent/service/news_service.dart index 03aa5259..fafc72ef 100644 --- a/lib/newsComponent/service/news_service.dart +++ b/lib/newsComponent/service/news_service.dart @@ -6,24 +6,24 @@ import 'package:campus_flutter/newsComponent/model/news_source.dart'; import 'package:campus_flutter/providers_get_it.dart'; class NewsService { - static Future<(DateTime?, List)> fetchNews(bool forcedRefresh) async { + static Future<(DateTime?, List)> fetchNews( + bool forcedRefresh) async { MainApi mainApi = getIt(); final response = await mainApi.makeRequest( TumCabeApi(tumCabeService: TumCabeServiceNewsSources()), NewsSources.fromJson, - forcedRefresh - ); + forcedRefresh); List newsSources = response.data.newsSources; for (var newsSource in newsSources.indexed) { try { final newsResponse = await mainApi.makeRequest( - TumCabeApi(tumCabeService: TumCabeServiceNews( - source: newsSource.$2.id.toString())), + TumCabeApi( + tumCabeService: + TumCabeServiceNews(source: newsSource.$2.id.toString())), NewsData.fromJson, - forcedRefresh - ); + forcedRefresh); newsSources[newsSource.$2.id].news.addAll(newsResponse.data.news); } catch (_) {} @@ -31,4 +31,4 @@ class NewsService { return (response.saved, newsSources); } -} \ No newline at end of file +} diff --git a/lib/newsComponent/viewModel/news_viewmodel.dart b/lib/newsComponent/viewModel/news_viewmodel.dart index 7638905d..e739479a 100644 --- a/lib/newsComponent/viewModel/news_viewmodel.dart +++ b/lib/newsComponent/viewModel/news_viewmodel.dart @@ -12,8 +12,7 @@ class NewsViewModel implements ViewModel { @override Future fetch(bool forcedRefresh) async { - NewsService.fetchNews(false) - .then((value) { + NewsService.fetchNews(false).then((value) { lastFetched.add(value.$1); newsSources.add(value.$2); }, onError: (error) => newsSources.addError(error)); @@ -21,7 +20,8 @@ class NewsViewModel implements ViewModel { List<(String?, News)> latestFiveNews() { if (newsSources.value != null) { - final news = newsSources.value!.expand((element) => element.news).toList(); + final news = + newsSources.value!.expand((element) => element.news).toList(); news.removeWhere((element) => (element.source == "2")); @@ -30,7 +30,13 @@ class NewsViewModel implements ViewModel { List fiveNews = news.sublist(0, 5); return fiveNews - .map((e) => (newsSources.value!.firstWhereOrNull((element) => e.source == element.id.toString())?.title, e)) + .map((e) => ( + newsSources.value! + .firstWhereOrNull( + (element) => e.source == element.id.toString()) + ?.title, + e + )) .toList(); } else { return []; diff --git a/lib/newsComponent/views/homeWidget/news_widget_view.dart b/lib/newsComponent/views/homeWidget/news_widget_view.dart index f2206172..f44c9f67 100644 --- a/lib/newsComponent/views/homeWidget/news_widget_view.dart +++ b/lib/newsComponent/views/homeWidget/news_widget_view.dart @@ -6,6 +6,7 @@ import 'package:campus_flutter/newsComponent/views/news_card_view.dart'; import 'package:campus_flutter/providers_get_it.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:campus_flutter/theme.dart'; class NewsWidgetView extends ConsumerStatefulWidget { const NewsWidgetView({super.key}); @@ -23,27 +24,46 @@ class _NewsWidgetViewState extends ConsumerState { @override Widget build(BuildContext context) { - return WidgetFrameView(title: "Latest News", child: - StreamBuilder( - stream: ref.watch(newsViewModel).newsSources, - builder: (context, snapshot) { - if (snapshot.hasData) { - final fiveNews = ref.watch(newsViewModel).latestFiveNews(); - if (fiveNews.isNotEmpty) { - return LayoutBuilder(builder: (context, constraints) { - return HorizontalSlider(data: fiveNews, height: 300, child: (news) { - return NewsCardView(news: news, width: constraints.maxWidth * 0.8); - }); - }); - } else { - return const SizedBox(height: 300, child: Card(child: Center(child: Text("no news found")))); - } - } else if (snapshot.hasError) { - return SizedBox(height: 300, child: Card(child: ErrorHandlingView(error: snapshot.error!, errorHandlingViewType: ErrorHandlingViewType.textOnly, retry: ref.read(newsViewModel).fetch))); - } else { - return const SizedBox(height: 300, child: Card(child: DelayedLoadingIndicator(name: "News"))); - } - } - )); + return WidgetFrameView( + title: context.localizations.latestNews, + child: StreamBuilder( + stream: ref.watch(newsViewModel).newsSources, + builder: (context, snapshot) { + if (snapshot.hasData) { + final fiveNews = ref.watch(newsViewModel).latestFiveNews(); + if (fiveNews.isNotEmpty) { + return LayoutBuilder(builder: (context, constraints) { + return HorizontalSlider( + data: fiveNews, + height: 300, + child: (news) { + return NewsCardView( + news: news, width: constraints.maxWidth * 0.8); + }); + }); + } else { + return SizedBox( + height: 300, + child: Card( + child: Center( + child: Text(context.localizations.noNewsFound)))); + } + } else if (snapshot.hasError) { + return SizedBox( + height: 300, + child: Card( + child: ErrorHandlingView( + error: snapshot.error!, + errorHandlingViewType: + ErrorHandlingViewType.textOnly, + retry: ref.read(newsViewModel).fetch))); + } else { + return SizedBox( + height: 300, + child: Card( + child: DelayedLoadingIndicator( + name: context.localizations.news))); + } + })); } -} \ No newline at end of file +} diff --git a/lib/newsComponent/views/news_card_view.dart b/lib/newsComponent/views/news_card_view.dart index 1bcc0f49..8d1e197f 100644 --- a/lib/newsComponent/views/news_card_view.dart +++ b/lib/newsComponent/views/news_card_view.dart @@ -4,6 +4,7 @@ import 'package:campus_flutter/newsComponent/model/news.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:campus_flutter/theme.dart'; class NewsCardView extends ConsumerWidget { const NewsCardView({super.key, required this.news, required this.width}); @@ -14,58 +15,72 @@ class NewsCardView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return GestureDetector( - onTap: () { - // TODO: with RSS feed we are able to link to TUM site - }, - child: AspectRatio( - aspectRatio: 1.1, - child: Card( - margin: const EdgeInsets.symmetric(vertical: 5.0), - child: Column( - children: [ - Expanded( - flex: 3, - child: ClipRRect( - borderRadius: const BorderRadius.vertical(top: Radius.circular(10.0)), - child: CachedNetworkImage( - imageUrl: kIsWeb - ? news.$2.image.toString() - .replaceAll("app.tum.de", "tum-proxy.resch.io") - : news.$2.image.toString(), - fadeOutDuration: Duration.zero, - fadeInDuration: Duration.zero, - placeholder: (context, string) => Image.asset("assets/images/placeholders/news_placeholder.png", fit: BoxFit.fill), - fit: BoxFit.fill - ))), - Expanded( - flex: 2, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: SizedBox( - width: MediaQuery.of(context).size.width, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 3, - child: Text(news.$2.title, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface), - maxLines: 2, - overflow: TextOverflow.ellipsis)), - Expanded( - child: Text(StringParser.dateFormatter(news.$2.created), - style: Theme.of(context).textTheme.bodySmall)), - Expanded( - child: Text("Source: ${news.$1}", - style: Theme.of(context).textTheme.bodySmall, - maxLines: 1, - overflow: TextOverflow.ellipsis)) - ], - )))) - ], - ), - ))); + onTap: () { + // TODO: with RSS feed we are able to link to TUM site + }, + child: AspectRatio( + aspectRatio: 1.1, + child: Card( + margin: const EdgeInsets.symmetric(vertical: 5.0), + child: Column( + children: [ + Expanded( + flex: 3, + child: ClipRRect( + borderRadius: const BorderRadius.vertical( + top: Radius.circular(10.0)), + child: CachedNetworkImage( + imageUrl: kIsWeb + ? news.$2.image.toString().replaceAll( + "app.tum.de", "tum-proxy.resch.io") + : news.$2.image.toString(), + fadeOutDuration: Duration.zero, + fadeInDuration: Duration.zero, + placeholder: (context, string) => Image.asset( + "assets/images/placeholders/news_placeholder.png", + fit: BoxFit.fill), + fit: BoxFit.fill))), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 3, + child: Text(news.$2.title, + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith( + fontWeight: FontWeight.w500, + color: Theme.of(context) + .colorScheme + .onSurface), + maxLines: 2, + overflow: TextOverflow.ellipsis)), + Expanded( + child: Text( + StringParser.dateFormatter( + news.$2.created), + style: Theme.of(context) + .textTheme + .bodySmall)), + Expanded( + child: Text( + context.localizations.source(news.$1), + style: Theme.of(context) + .textTheme + .bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis)) + ], + )))) + ], + ), + ))); } } diff --git a/lib/personDetailedComponent/model/organisation.dart b/lib/personDetailedComponent/model/organisation.dart index 74da05cb..d25e1877 100644 --- a/lib/personDetailedComponent/model/organisation.dart +++ b/lib/personDetailedComponent/model/organisation.dart @@ -1,4 +1,3 @@ - import 'package:json_annotation/json_annotation.dart'; part 'organisation.g.dart'; @@ -16,15 +15,15 @@ class Organisation { @JsonKey(name: "beschreibung") final String? description; - Organisation({ - required this.name, - required this.id, - required this.number, - required this.title, - required this.description - }); + Organisation( + {required this.name, + required this.id, + required this.number, + required this.title, + required this.description}); - factory Organisation.fromJson(Map json) => _$OrganisationFromJson(json); + factory Organisation.fromJson(Map json) => + _$OrganisationFromJson(json); Map toJson() => _$OrganisationToJson(this); -} \ No newline at end of file +} diff --git a/lib/personDetailedComponent/model/person_details.dart b/lib/personDetailedComponent/model/person_details.dart index 58fb229e..a6ac660f 100644 --- a/lib/personDetailedComponent/model/person_details.dart +++ b/lib/personDetailedComponent/model/person_details.dart @@ -17,11 +17,13 @@ class PersonDetails { assert(split.length == 2); return split.first; } + String? get id { final split = obfuscatedID.split("*"); assert(split.length == 2); return split.last; } + @JsonKey(name: "vorname") final String firstName; @JsonKey(name: "familienname") @@ -47,28 +49,28 @@ class PersonDetails { @JsonKey(name: "telefon_nebenstellen") final List? phoneExtensions; - PersonDetails({ - required this.nr, - required this.obfuscatedID, - required this.firstName, - required this.name, - this.title, - required this.email, - required this.gender, - this.officeHours, - //required this.officialContact, - //required this.privateContact, - this.imageData, - this.organisations, - this.rooms, - required this.phoneExtensions - }); + PersonDetails( + {required this.nr, + required this.obfuscatedID, + required this.firstName, + required this.name, + this.title, + required this.email, + required this.gender, + this.officeHours, + //required this.officialContact, + //required this.privateContact, + this.imageData, + this.organisations, + this.rooms, + required this.phoneExtensions}); String get fullName { return "$firstName $name"; } - factory PersonDetails.fromJson(Map json) => _$PersonDetailsFromJson(json); + factory PersonDetails.fromJson(Map json) => + _$PersonDetailsFromJson(json); Map toJson() => _$PersonDetailsToJson(this); @@ -89,9 +91,7 @@ class PersonDetails { try { json = json["raum"]; if (json is List) { - return json - .map((e) => Room.fromJson(e)) - .toList(); + return json.map((e) => Room.fromJson(e)).toList(); } else if (json is Map) { return [Room.fromJson(json)]; } else { @@ -106,9 +106,7 @@ class PersonDetails { try { json = json["gruppe"]; if (json is List) { - return json - .map((e) => Organisation.fromJson(e)) - .toList(); + return json.map((e) => Organisation.fromJson(e)).toList(); } else if (json is Map) { return [Organisation.fromJson(json)]; } else { @@ -127,7 +125,8 @@ class PersonDetailsData { PersonDetailsData({required this.person}); - factory PersonDetailsData.fromJson(Map json) => _$PersonDetailsDataFromJson(json); + factory PersonDetailsData.fromJson(Map json) => + _$PersonDetailsDataFromJson(json); Map toJson() => _$PersonDetailsDataToJson(this); -} \ No newline at end of file +} diff --git a/lib/personDetailedComponent/model/phone_extension.dart b/lib/personDetailedComponent/model/phone_extension.dart index f3f64c6d..d63ba870 100644 --- a/lib/personDetailedComponent/model/phone_extension.dart +++ b/lib/personDetailedComponent/model/phone_extension.dart @@ -17,15 +17,15 @@ class PhoneExtension { @JsonKey(name: "tum_nebenstelle") final String branchNumber; - PhoneExtension({ - required this.phoneNumber, - required this.countryCode, - required this.areaCode, - required this.equipmentNumber, - required this.branchNumber - }); + PhoneExtension( + {required this.phoneNumber, + required this.countryCode, + required this.areaCode, + required this.equipmentNumber, + required this.branchNumber}); - factory PhoneExtension.fromJson(Map json) => _$PhoneExtensionFromJson(json); + factory PhoneExtension.fromJson(Map json) => + _$PhoneExtensionFromJson(json); Map toJson() => _$PhoneExtensionToJson(this); -} \ No newline at end of file +} diff --git a/lib/personDetailedComponent/model/room.dart b/lib/personDetailedComponent/model/room.dart index a17c82a9..fd3138e9 100644 --- a/lib/personDetailedComponent/model/room.dart +++ b/lib/personDetailedComponent/model/room.dart @@ -23,19 +23,18 @@ class Room { @JsonKey(name: "lang") final String longLocationDescription; - Room({ - required this.number, - required this.buildingName, - required this.buildingNumber, - required this.floorName, - required this.floorNumber, - required this.id, - required this.locationDescription, - required this.shortLocationDescription, - required this.longLocationDescription - }); + Room( + {required this.number, + required this.buildingName, + required this.buildingNumber, + required this.floorName, + required this.floorNumber, + required this.id, + required this.locationDescription, + required this.shortLocationDescription, + required this.longLocationDescription}); factory Room.fromJson(Map json) => _$RoomFromJson(json); Map toJson() => _$RoomToJson(this); -} \ No newline at end of file +} diff --git a/lib/personDetailedComponent/services/person_details_service.dart b/lib/personDetailedComponent/services/person_details_service.dart index 2a689016..04a8b735 100644 --- a/lib/personDetailedComponent/services/person_details_service.dart +++ b/lib/personDetailedComponent/services/person_details_service.dart @@ -6,14 +6,15 @@ import 'package:campus_flutter/personDetailedComponent/model/person_details.dart import 'package:campus_flutter/providers_get_it.dart'; class PersonDetailsService { - static Future<(DateTime?, PersonDetails)> fetchPersonDetails(bool forcedRefresh, String identNumber) async { + static Future<(DateTime?, PersonDetails)> fetchPersonDetails( + bool forcedRefresh, String identNumber) async { MainApi mainApi = getIt(); - final response = await mainApi - .makeRequestWithException( - TumOnlineApi(TumOnlineServicePersonDetails(identNumber: identNumber)), - PersonDetailsData.fromJson, - TumOnlineApiException.fromJson, - forcedRefresh); + final response = await mainApi.makeRequestWithException( + TumOnlineApi(TumOnlineServicePersonDetails(identNumber: identNumber)), + PersonDetailsData.fromJson, + TumOnlineApiException.fromJson, + forcedRefresh); return (response.saved, response.data.person); } -} \ No newline at end of file +} diff --git a/lib/placesComponent/map.dart b/lib/placesComponent/map.dart index 87e1df6e..2827b970 100644 --- a/lib/placesComponent/map.dart +++ b/lib/placesComponent/map.dart @@ -136,4 +136,4 @@ class _MapWidgetState extends State with WidgetsBindingObserver { markers: _markers, ); } -}*/ \ No newline at end of file +}*/ diff --git a/lib/placesComponent/model/cafeterias/cafeteria.dart b/lib/placesComponent/model/cafeterias/cafeteria.dart index d28185ad..b71fe83d 100644 --- a/lib/placesComponent/model/cafeterias/cafeteria.dart +++ b/lib/placesComponent/model/cafeterias/cafeteria.dart @@ -8,9 +8,11 @@ class Location { final double longitude; final String address; - Location({required this.latitude, required this.longitude, required this.address}); + Location( + {required this.latitude, required this.longitude, required this.address}); - factory Location.fromJson(Map json) => _$LocationFromJson(json); + factory Location.fromJson(Map json) => + _$LocationFromJson(json); Map toJson() => _$LocationToJson(this); } @@ -41,9 +43,15 @@ class Cafeteria { return name; } - Cafeteria({required this.location, required this.name, required this.id, required this.queueStatusApi, required this.queue}); + Cafeteria( + {required this.location, + required this.name, + required this.id, + required this.queueStatusApi, + required this.queue}); - factory Cafeteria.fromJson(Map json) => _$CafeteriaFromJson(json); + factory Cafeteria.fromJson(Map json) => + _$CafeteriaFromJson(json); Map toJson() => _$CafeteriaToJson(this); } @@ -55,7 +63,8 @@ class Cafeterias { Cafeterias({required this.cafeterias}); - factory Cafeterias.fromJson(Map json) => _$CafeteriasFromJson(json); + factory Cafeterias.fromJson(Map json) => + _$CafeteriasFromJson(json); Map toJson() => _$CafeteriasToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/cafeterias/cafeteria_menu.dart b/lib/placesComponent/model/cafeterias/cafeteria_menu.dart index 69c6a4d5..17198f58 100644 --- a/lib/placesComponent/model/cafeterias/cafeteria_menu.dart +++ b/lib/placesComponent/model/cafeterias/cafeteria_menu.dart @@ -5,8 +5,5 @@ class CafeteriaMenu { final DateTime date; final List categories; - CafeteriaMenu({ - required this.date, - required this.categories - }); -} \ No newline at end of file + CafeteriaMenu({required this.date, required this.categories}); +} diff --git a/lib/placesComponent/model/cafeterias/dish.dart b/lib/placesComponent/model/cafeterias/dish.dart index 6a93d257..22b3307a 100644 --- a/lib/placesComponent/model/cafeterias/dish.dart +++ b/lib/placesComponent/model/cafeterias/dish.dart @@ -9,8 +9,12 @@ class Dish { final List labels; @JsonKey(name: "dish_type") final String dishType; - - Dish({required this.name, required this.prices, required this.labels, required this.dishType}); + + Dish( + {required this.name, + required this.prices, + required this.labels, + required this.dishType}); factory Dish.fromJson(Map json) => _$DishFromJson(json); @@ -30,4 +34,4 @@ class Price { factory Price.fromJson(Map json) => _$PriceFromJson(json); Map toJson() => _$PriceToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/cafeterias/dish_label.dart b/lib/placesComponent/model/cafeterias/dish_label.dart index 674b081e..47311c00 100644 --- a/lib/placesComponent/model/cafeterias/dish_label.dart +++ b/lib/placesComponent/model/cafeterias/dish_label.dart @@ -9,9 +9,11 @@ class DishLabel { final Map text; final String abbreviation; - DishLabel({required this.name, required this.text, required this.abbreviation}); + DishLabel( + {required this.name, required this.text, required this.abbreviation}); - factory DishLabel.fromJson(Map json) => _$DishLabelFromJson(json); + factory DishLabel.fromJson(Map json) => + _$DishLabelFromJson(json); Map toJson() => _$DishLabelToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/cafeterias/meal_plan.dart b/lib/placesComponent/model/cafeterias/meal_plan.dart index a4356264..3a7e2b6e 100644 --- a/lib/placesComponent/model/cafeterias/meal_plan.dart +++ b/lib/placesComponent/model/cafeterias/meal_plan.dart @@ -9,10 +9,11 @@ class MealPlan { final int week; final int year; final List days; - + MealPlan({required this.week, required this.year, required this.days}); - factory MealPlan.fromJson(Map json) => _$MealPlanFromJson(json); + factory MealPlan.fromJson(Map json) => + _$MealPlanFromJson(json); Map toJson() => _$MealPlanToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/cafeterias/mensa_menu.dart b/lib/placesComponent/model/cafeterias/mensa_menu.dart index 409c14f7..c349db26 100644 --- a/lib/placesComponent/model/cafeterias/mensa_menu.dart +++ b/lib/placesComponent/model/cafeterias/mensa_menu.dart @@ -7,10 +7,11 @@ part 'mensa_menu.g.dart'; class MensaMenu { final DateTime date; final List dishes; - + MensaMenu({required this.date, required this.dishes}); - factory MensaMenu.fromJson(Map json) => _$MensaMenuFromJson(json); + factory MensaMenu.fromJson(Map json) => + _$MensaMenuFromJson(json); Map toJson() => _$MensaMenuToJson(this); } @@ -22,7 +23,8 @@ class MenuCategory { MenuCategory({required this.name, required this.dishes}); - factory MenuCategory.fromJson(Map json) => _$MenuCategoryFromJson(json); + factory MenuCategory.fromJson(Map json) => + _$MenuCategoryFromJson(json); Map toJson() => _$MenuCategoryToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/studyRooms/study_room.dart b/lib/placesComponent/model/studyRooms/study_room.dart index abee8d4f..54625022 100644 --- a/lib/placesComponent/model/studyRooms/study_room.dart +++ b/lib/placesComponent/model/studyRooms/study_room.dart @@ -1,5 +1,6 @@ import 'package:campus_flutter/placesComponent/model/studyRooms/study_room_attribute.dart'; -import 'package:intl/intl.dart'; +import 'package:campus_flutter/theme.dart'; +import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; part 'study_room.g.dart'; @@ -36,18 +37,18 @@ class StudyRoom { @JsonKey(name: "attribute") final List? attributes; - String get localizedStatus { + String localizedStatus(BuildContext context) { switch (status) { case "frei": - return "Free"; + return context.localizations.free; case "belegt": if (occupiedUntil != null) { - return "Occupied until ${ DateFormat.yMd().format(occupiedUntil!)}"; + return context.localizations.occupiedUntil(occupiedUntil!); } else { - return ""; + return context.localizations.unknown; } default: - return "Unknown"; + return context.localizations.unknown; } } @@ -55,9 +56,8 @@ class StudyRoom { return status == "frei"; } - - StudyRoom({ - this.buildingCode, + StudyRoom( + {this.buildingCode, this.buildingName, required this.buildingNumber, this.code, @@ -72,10 +72,10 @@ class StudyRoom { this.raum_nr_architekt, required this.res_nr, this.status, - this.attributes - }); + this.attributes}); - factory StudyRoom.fromJson(Map json) => _$StudyRoomFromJson(json); + factory StudyRoom.fromJson(Map json) => + _$StudyRoomFromJson(json); Map toJson() => _$StudyRoomToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/studyRooms/study_room_attribute.dart b/lib/placesComponent/model/studyRooms/study_room_attribute.dart index 41b5d3cc..b8aaa63c 100644 --- a/lib/placesComponent/model/studyRooms/study_room_attribute.dart +++ b/lib/placesComponent/model/studyRooms/study_room_attribute.dart @@ -7,12 +7,10 @@ class StudyRoomAttribute { final String? detail; final String? name; - StudyRoomAttribute({ - this.detail, - this.name - }); + StudyRoomAttribute({this.detail, this.name}); - factory StudyRoomAttribute.fromJson(Map json) => _$StudyRoomAttributeFromJson(json); + factory StudyRoomAttribute.fromJson(Map json) => + _$StudyRoomAttributeFromJson(json); Map toJson() => _$StudyRoomAttributeToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/studyRooms/study_room_data.dart b/lib/placesComponent/model/studyRooms/study_room_data.dart index 7757c7f5..98e43d85 100644 --- a/lib/placesComponent/model/studyRooms/study_room_data.dart +++ b/lib/placesComponent/model/studyRooms/study_room_data.dart @@ -11,12 +11,10 @@ class StudyRoomData { @JsonKey(name: "gruppen") final List? groups; - StudyRoomData({ - this.rooms, - this.groups - }); + StudyRoomData({this.rooms, this.groups}); - factory StudyRoomData.fromJson(Map json) => _$StudyRoomDataFromJson(json); + factory StudyRoomData.fromJson(Map json) => + _$StudyRoomDataFromJson(json); Map toJson() => _$StudyRoomDataToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/studyRooms/study_room_group.dart b/lib/placesComponent/model/studyRooms/study_room_group.dart index f4df656d..970e852a 100644 --- a/lib/placesComponent/model/studyRooms/study_room_group.dart +++ b/lib/placesComponent/model/studyRooms/study_room_group.dart @@ -18,17 +18,22 @@ class StudyRoomGroup { Location? get coordinate { switch (id) { case 44: - return Location(latitude: 48.24926355557732, longitude: 11.633834370828435); + return Location( + latitude: 48.24926355557732, longitude: 11.633834370828435); case 46: return Location(latitude: 48.2629811953867, longitude: 11.6668123); case 47: - return Location(latitude: 48.26250533403169, longitude: 11.668024666454896); + return Location( + latitude: 48.26250533403169, longitude: 11.668024666454896); case 60: - return Location(latitude: 48.14778663798231, longitude: 11.56695764027295); + return Location( + latitude: 48.14778663798231, longitude: 11.56695764027295); case 97: - return Location(latitude: 48.26696368721545, longitude: 11.670222023419445); + return Location( + latitude: 48.26696368721545, longitude: 11.670222023419445); case 130: - return Location(latitude: 48.39535098293569, longitude: 11.724272313959853); + return Location( + latitude: 48.39535098293569, longitude: 11.724272313959853); default: return null; } @@ -39,15 +44,15 @@ class StudyRoomGroup { return allRooms; } - StudyRoomGroup({ - this.detail, - required this.id, - this.name, - required this.sorting, - this.rooms - }); + StudyRoomGroup( + {this.detail, + required this.id, + this.name, + required this.sorting, + this.rooms}); - factory StudyRoomGroup.fromJson(Map json) => _$StudyRoomGroupFromJson(json); + factory StudyRoomGroup.fromJson(Map json) => + _$StudyRoomGroupFromJson(json); Map toJson() => _$StudyRoomGroupToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/model/studyRooms/study_room_image_mapping.dart b/lib/placesComponent/model/studyRooms/study_room_image_mapping.dart index 8e499c89..bc9889f4 100644 --- a/lib/placesComponent/model/studyRooms/study_room_image_mapping.dart +++ b/lib/placesComponent/model/studyRooms/study_room_image_mapping.dart @@ -11,16 +11,15 @@ class StudyRoomImageMapping { final int width; final int heigth; + StudyRoomImageMapping( + {required this.id, + required this.description, + required this.scale, + required this.width, + required this.heigth}); - StudyRoomImageMapping({ - required this.id, - required this.description, - required this.scale, - required this.width, - required this.heigth - }); - - factory StudyRoomImageMapping.fromJson(Map json) => _$StudyRoomImageMappingFromJson(json); + factory StudyRoomImageMapping.fromJson(Map json) => + _$StudyRoomImageMappingFromJson(json); Map toJson() => _$StudyRoomImageMappingToJson(this); -} \ No newline at end of file +} diff --git a/lib/placesComponent/services/cafeterias_service.dart b/lib/placesComponent/services/cafeterias_service.dart index 585cb0f8..8217ee14 100644 --- a/lib/placesComponent/services/cafeterias_service.dart +++ b/lib/placesComponent/services/cafeterias_service.dart @@ -5,16 +5,14 @@ import 'package:campus_flutter/placesComponent/model/cafeterias/cafeteria.dart'; import 'package:campus_flutter/providers_get_it.dart'; class CafeteriasService { - static Future<(DateTime?, List)> fetchCafeterias(bool forcedRefresh) async { + static Future<(DateTime?, List)> fetchCafeterias( + bool forcedRefresh) async { MainApi mainApi = getIt(); final response = await mainApi.makeRequest( - EatApi(EatApiServiceCanteens()), - Cafeterias.fromJson, - forcedRefresh - ); + EatApi(EatApiServiceCanteens()), Cafeterias.fromJson, forcedRefresh); // TODO: add fetching of queue status return (response.saved, response.data.cafeterias); } -} \ No newline at end of file +} diff --git a/lib/placesComponent/services/mealplan_service.dart b/lib/placesComponent/services/mealplan_service.dart index 557a8f47..c1603560 100644 --- a/lib/placesComponent/services/mealplan_service.dart +++ b/lib/placesComponent/services/mealplan_service.dart @@ -10,13 +10,15 @@ import 'package:campus_flutter/placesComponent/model/cafeterias/mensa_menu.dart' import 'package:campus_flutter/providers_get_it.dart'; class MealPlanService { - static Future<(DateTime?, List)> getCafeteriaMenu(bool forcedRefresh, - Cafeteria cafeteria) async { + static Future<(DateTime?, List)> getCafeteriaMenu( + bool forcedRefresh, Cafeteria cafeteria) async { MainApi mainApi = getIt(); final today = DateTime.now(); final response = await mainApi.makeRequest( - EatApi( - EatApiServiceMenu(location: cafeteria.id, year: today.year, week: today.weekNumber())), + EatApi(EatApiServiceMenu( + location: cafeteria.id, + year: today.year, + week: today.weekNumber())), MealPlan.fromJson, forcedRefresh); @@ -27,13 +29,14 @@ class MealPlanService { final nextWeekResponse = await mainApi.makeRequest( EatApi(EatApiServiceMenu( - location: cafeteria.id, year: nextWeek.year, week: nextWeek.weekNumber() - )), + location: cafeteria.id, + year: nextWeek.year, + week: nextWeek.weekNumber())), MealPlan.fromJson, forcedRefresh); - final List nextWeekMenu = - _filterNextWeekMenu(_getMenuPerDay(nextWeekResponse.data), thisWeekMenu); + final List nextWeekMenu = _filterNextWeekMenu( + _getMenuPerDay(nextWeekResponse.data), thisWeekMenu); thisWeekMenu.addAll(nextWeekMenu); @@ -46,7 +49,8 @@ class MealPlanService { static List _filterNextWeekMenu( List nextWeekMenu, List thisWeekMenu) { List filteredMenu = nextWeekMenu.where((menu) { - return !thisWeekMenu.any((thisWeekMenu) => thisWeekMenu.date == menu.date); + return !thisWeekMenu + .any((thisWeekMenu) => thisWeekMenu.date == menu.date); }).toList(); return filteredMenu; @@ -58,7 +62,7 @@ class MealPlanService { final todayDate = DateTime(today.year, today.month, today.day); mealPlan.days.removeWhere((element) => (element.dishes.isEmpty || - (element.date != todayDate || element.date.isBefore(todayDate)))); + (element.date != todayDate || element.date.isBefore(todayDate)))); mealPlan.days.sort((menu1, menu2) => menu1.date.compareTo(menu2.date)); cafeteriaMenu = mealPlan.days.map((e) { diff --git a/lib/placesComponent/services/studyrooms_service.dart b/lib/placesComponent/services/studyrooms_service.dart index 5bd07195..6abf1f32 100644 --- a/lib/placesComponent/services/studyrooms_service.dart +++ b/lib/placesComponent/services/studyrooms_service.dart @@ -1,32 +1,18 @@ -import 'package:campus_flutter/base/networking/apis/tumCabeApi/tum_cabe_api.dart'; -import 'package:campus_flutter/base/networking/apis/tumCabeApi/tum_cabe_api_service.dart'; import 'package:campus_flutter/base/networking/apis/tumDevAppApi/tum_dev_app_api.dart'; import 'package:campus_flutter/base/networking/apis/tumDevAppApi/tum_dev_app_api_service.dart'; import 'package:campus_flutter/base/networking/protocols/main_api.dart'; import 'package:campus_flutter/placesComponent/model/studyRooms/study_room_data.dart'; -import 'package:campus_flutter/placesComponent/model/studyRooms/study_room_image_mapping.dart'; import 'package:campus_flutter/providers_get_it.dart'; class StudyRoomsService { - static Future<(DateTime?, StudyRoomData)> fetchStudyRooms(bool forcedRefresh) async { + static Future<(DateTime?, StudyRoomData)> fetchStudyRooms( + bool forcedRefresh) async { MainApi mainApi = getIt(); final response = await mainApi.makeRequest( TumDevAppApi(tumDevAppService: TumDevAppServiceRooms()), StudyRoomData.fromJson, - forcedRefresh - ); + forcedRefresh); return (response.saved, response.data); } - - static Future<(DateTime?, StudyRoomImageMapping)> fetchMap(bool forcedRefresh, String room) async { - MainApi mainApi = getIt(); - final response = await mainApi.makeRequest( - TumCabeApi(tumCabeService: TumCabeServiceRoomMaps(room: room)), - StudyRoomImageMapping.fromJson, - forcedRefresh - ); - - return (response.saved, response.data); - } -} \ No newline at end of file +} diff --git a/lib/placesComponent/viewModels/cafeteria_widget_viewmodel.dart b/lib/placesComponent/viewModels/cafeteria_widget_viewmodel.dart index 40f92c0d..ca4c7c4b 100644 --- a/lib/placesComponent/viewModels/cafeteria_widget_viewmodel.dart +++ b/lib/placesComponent/viewModels/cafeteria_widget_viewmodel.dart @@ -24,7 +24,8 @@ class CafeteriaWidgetViewModel implements ViewModel { }, onError: (error) => cafeteria.addError(error)); } - _getClosestCafeteria(bool forcedRefresh, (DateTime?, List) response, Position? location) { + _getClosestCafeteria(bool forcedRefresh, + (DateTime?, List) response, Position? location) { lastFetched.add(response.$1); if (location != null) { @@ -33,15 +34,13 @@ class CafeteriaWidgetViewModel implements ViewModel { currentCafeteria.location.latitude, currentCafeteria.location.longitude, location.latitude, - location.longitude - ); + location.longitude); final distanceNext = Geolocator.distanceBetween( nextCafeteria.location.latitude, nextCafeteria.location.longitude, location.latitude, - location.longitude - ); + location.longitude); if (distanceCurrent < distanceNext) { return currentCafeteria; @@ -139,13 +138,18 @@ class CafeteriaWidgetViewModel implements ViewModel { } if (price.unitPrice != null && price.unit != null && price.unitPrice != 0) { - unitPriceString = '${priceFormatter.format(price.unitPrice!)} / ${price.unit!}'; + unitPriceString = + '${priceFormatter.format(price.unitPrice!)} / ${price.unit!}'; } - final divider = (basePriceString?.isNotEmpty == true && unitPriceString?.isNotEmpty == true) ? ' + ' : ''; + final divider = (basePriceString?.isNotEmpty == true && + unitPriceString?.isNotEmpty == true) + ? ' + ' + : ''; - final finalPrice = (basePriceString ?? '') + divider + (unitPriceString ?? ''); + final finalPrice = + (basePriceString ?? '') + divider + (unitPriceString ?? ''); return finalPrice; } -} \ No newline at end of file +} diff --git a/lib/placesComponent/viewModels/studyroom_widget_viewmodel.dart b/lib/placesComponent/viewModels/studyroom_widget_viewmodel.dart index 4e39b617..f33c8560 100644 --- a/lib/placesComponent/viewModels/studyroom_widget_viewmodel.dart +++ b/lib/placesComponent/viewModels/studyroom_widget_viewmodel.dart @@ -8,7 +8,8 @@ import 'package:geolocator/geolocator.dart'; import 'package:rxdart/rxdart.dart'; class StudyRoomWidgetViewModel implements ViewModel { - BehaviorSubject studyRoomGroup = BehaviorSubject.seeded(null); + BehaviorSubject studyRoomGroup = + BehaviorSubject.seeded(null); BehaviorSubject?> rooms = BehaviorSubject.seeded(null); final BehaviorSubject lastFetched = BehaviorSubject.seeded(null); @@ -26,14 +27,24 @@ class StudyRoomWidgetViewModel implements ViewModel { _getClosestRooms((DateTime?, StudyRoomData) response, Position? location) { lastFetched.add(response.$1); - if (response.$2.groups != null && response.$2.rooms != null && location != null) { + if (response.$2.groups != null && + response.$2.rooms != null && + location != null) { final group = response.$2.groups?.reduce((currentGroup, nextGroup) { final distanceCurrent = currentGroup.coordinate != null - ? Geolocator.distanceBetween(currentGroup.coordinate!.latitude, currentGroup.coordinate!.longitude, location.latitude, location.longitude) + ? Geolocator.distanceBetween( + currentGroup.coordinate!.latitude, + currentGroup.coordinate!.longitude, + location.latitude, + location.longitude) : 0.0; final distanceNext = nextGroup.coordinate != null - ? Geolocator.distanceBetween(nextGroup.coordinate!.latitude, nextGroup.coordinate!.longitude, location.latitude, location.longitude) + ? Geolocator.distanceBetween( + nextGroup.coordinate!.latitude, + nextGroup.coordinate!.longitude, + location.latitude, + location.longitude) : 0.0; if (distanceCurrent < distanceNext) { @@ -46,19 +57,14 @@ class StudyRoomWidgetViewModel implements ViewModel { studyRoomGroup.add(group); final rooms = group?.getRooms(response.$2.rooms!); rooms?.sort((room1, room2) { - if (room1.localizedStatus == "Free" && room2.localizedStatus == "Free") { - return 0; - } else if (room1.localizedStatus != "Free" && room2.localizedStatus == "Free") { - return 1; - } else if (room1.localizedStatus == "Free" && room2.localizedStatus != "Free") { - return -1; - } else if (room1.localizedStatus != "Unknown" && room2.localizedStatus == "Unknown") { - return -1; - } else if (room1.localizedStatus == "Unknown" && room2.localizedStatus != "Unknown") { - return 1; - } else { - return 0; - } + Map statusOrder = { + "frei": 0, + "belegt": 1, + "unbekannt": 2, + }; + int statusValue1 = statusOrder[room1.status] ?? 3; + int statusValue2 = statusOrder[room2.status] ?? 3; + return statusValue1.compareTo(statusValue2); }); this.rooms.add(rooms ?? []); diff --git a/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart b/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart index 4b39b483..5ce97f79 100644 --- a/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart +++ b/lib/placesComponent/views/homeWidget/cafeteria_widget_view.dart @@ -7,6 +7,7 @@ import 'package:campus_flutter/placesComponent/model/cafeterias/cafeteria_menu.d import 'package:campus_flutter/placesComponent/model/cafeterias/dish.dart'; import 'package:campus_flutter/placesComponent/viewModels/cafeteria_widget_viewmodel.dart'; import 'package:campus_flutter/providers_get_it.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -14,11 +15,11 @@ class CafeteriaWidgetView extends ConsumerStatefulWidget { const CafeteriaWidgetView({super.key}); @override - ConsumerState createState() => _CafeteriaWidgetViewState(); + ConsumerState createState() => + _CafeteriaWidgetViewState(); } class _CafeteriaWidgetViewState extends ConsumerState { - @override void initState() { ref.read(cafeteriaWidgetViewModel).fetch(false); @@ -32,11 +33,11 @@ class _CafeteriaWidgetViewState extends ConsumerState { stream: ref.watch(cafeteriaWidgetViewModel).cafeteriaMenu, builder: (context, snapshot) { return WidgetFrameView( - title: ref.watch(cafeteriaWidgetViewModel).cafeteria.value?.name ?? "Cafeteria", - child: _dynamicContent(snapshot) - ); - } - ); + title: + ref.watch(cafeteriaWidgetViewModel).cafeteria.value?.name ?? + context.localizations.cafeteria, + child: _dynamicContent(snapshot)); + }); } // TODO: change to adaptive @@ -51,20 +52,25 @@ class _CafeteriaWidgetViewState extends ConsumerState { return _dishCard(dish); }); } else { - return const Card( - child: SizedBox(height: 150, child: Center(child: Text("no meal plan found")))); + return Card( + child: SizedBox( + height: 150, + child: Center( + child: Text(context.localizations.noMealPlanFound)))); } } else if (snapshot.hasError) { // TODO: error handling if offline - return Card(child: SizedBox(height: 150, child: - ErrorHandlingView( - error: snapshot.error!, - errorHandlingViewType: ErrorHandlingViewType.descriptionOnly, - retry: ref.read(cafeteriaWidgetViewModel).fetch - ))); + return Card( + child: SizedBox( + height: 150, + child: ErrorHandlingView( + error: snapshot.error!, + errorHandlingViewType: ErrorHandlingViewType.descriptionOnly, + retry: ref.read(cafeteriaWidgetViewModel).fetch))); } else { return const Card( - child: SizedBox(height: 150, child: DelayedLoadingIndicator(name: "Mealplan"))); + child: SizedBox( + height: 150, child: DelayedLoadingIndicator(name: "Mealplan"))); } } @@ -72,31 +78,43 @@ class _CafeteriaWidgetViewState extends ConsumerState { return CardWithPadding( height: 150, margin: const EdgeInsets.symmetric(vertical: 5.0), - child: SizedBox(width: 150, child: - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: - Row( - mainAxisAlignment: MainAxisAlignment.center, + child: SizedBox( + width: 150, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(dish.$2, style: Theme.of(context).textTheme.titleLarge), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(dish.$2, + style: Theme.of(context).textTheme.titleLarge), const Spacer(), IconButton( - onPressed: () => _dishInfoAlert(dish.$1, context), - icon: Icon(Icons.info_outline, color: Theme.of(context).primaryColor), - padding: EdgeInsets.zero, - alignment: Alignment.centerRight, - highlightColor: Colors.transparent, + onPressed: () => _dishInfoAlert(dish.$1, context), + icon: Icon(Icons.info_outline, + color: Theme.of(context).primaryColor), + padding: EdgeInsets.zero, + alignment: Alignment.centerRight, + highlightColor: Colors.transparent, ) ], )), - const Padding(padding: EdgeInsets.symmetric(vertical: 5)), - Expanded(flex: 3, child: Text(dish.$1.name, maxLines: 3, overflow: TextOverflow.ellipsis,)), - Expanded(child: Text(CafeteriaWidgetViewModel.formatPrice(dish.$1), maxLines: 1,)) - ], - ) - )); + const Padding(padding: EdgeInsets.symmetric(vertical: 5)), + Expanded( + flex: 3, + child: Text( + dish.$1.name, + maxLines: 3, + overflow: TextOverflow.ellipsis, + )), + Expanded( + child: Text( + CafeteriaWidgetViewModel.formatPrice(dish.$1), + maxLines: 1, + )) + ], + ))); } _dishInfoAlert(Dish dish, BuildContext context) { @@ -106,19 +124,16 @@ class _CafeteriaWidgetViewState extends ConsumerState { return AlertDialog( title: Text(dish.name), actionsAlignment: MainAxisAlignment.center, - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (var label in dish.labels) ...[ - Text(label) - ], - Text(CafeteriaWidgetViewModel.formatPrice(dish)) + content: Column(mainAxisSize: MainAxisSize.min, children: [ + for (var label in dish.labels) ...[Text(label)], + Text(CafeteriaWidgetViewModel.formatPrice(dish)) ]), actions: [ - TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("Okay")) + TextButton( + child: const Text("Okay"), + onPressed: () => Navigator.of(context).pop()) ], ); - } - ); + }); } -} \ No newline at end of file +} diff --git a/lib/placesComponent/views/homeWidget/studyroom_widget_view.dart b/lib/placesComponent/views/homeWidget/studyroom_widget_view.dart index 6fadc899..e3900ceb 100644 --- a/lib/placesComponent/views/homeWidget/studyroom_widget_view.dart +++ b/lib/placesComponent/views/homeWidget/studyroom_widget_view.dart @@ -5,6 +5,7 @@ import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_ 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:campus_flutter/providers_get_it.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -12,7 +13,8 @@ class StudyRoomWidgetView extends ConsumerStatefulWidget { const StudyRoomWidgetView({super.key}); @override - ConsumerState createState() => _StudyRoomWidgetViewState(); + ConsumerState createState() => + _StudyRoomWidgetViewState(); } class _StudyRoomWidgetViewState extends ConsumerState { @@ -25,7 +27,7 @@ class _StudyRoomWidgetViewState extends ConsumerState { @override Widget build(BuildContext context) { return WidgetFrameView( - title: "Nearest Study Rooms", + title: context.localizations.nearestStudyRooms, child: StreamBuilder( stream: ref.watch(studyRoomWidgetViewModel).studyRoomGroup, builder: (context, snapshot) { @@ -41,19 +43,20 @@ class _StudyRoomWidgetViewState extends ConsumerState { })); } - Widget _widgetLabel(AsyncSnapshot snapshot, BuildContext context) { + Widget _widgetLabel( + AsyncSnapshot snapshot, BuildContext context) { if (snapshot.hasData) { if (snapshot.data != null) { return _buttonLabel(context, snapshot); } else { - return const Center(child: Text("no study rooms near you found")); + return Center( + child: Text(context.localizations.noNearFreeStudyRoomsFound)); } } else if (snapshot.hasError) { return ErrorHandlingView( error: snapshot.error!, errorHandlingViewType: ErrorHandlingViewType.descriptionOnly, - retry: ref.read(studyRoomWidgetViewModel).fetch - ); + retry: ref.read(studyRoomWidgetViewModel).fetch); } else { return const DelayedLoadingIndicator(name: "Closest Study Room"); } @@ -62,17 +65,20 @@ class _StudyRoomWidgetViewState extends ConsumerState { _onPressed(BuildContext context) { if (MediaQuery.orientationOf(context) == Orientation.portrait) { Navigator.of(context).push(MaterialPageRoute( - builder: (context) => - const StudyRoomGroupScaffold())); + builder: (context) => const StudyRoomGroupScaffold())); } else { - ref.read(homeSplitViewModel).selectedWidget.add(const StudyRoomGroupView()); + ref + .read(homeSplitViewModel) + .selectedWidget + .add(const StudyRoomGroupView()); } } - Widget _buttonLabel(BuildContext context, AsyncSnapshot snapshot) { + Widget _buttonLabel( + BuildContext context, AsyncSnapshot snapshot) { return Row( children: [ - Text(snapshot.data?.name ?? "Unkown"), + Text(snapshot.data?.name ?? context.localizations.unknown), const Spacer(), _freeRooms(snapshot), const Padding(padding: EdgeInsets.symmetric(horizontal: 5.0)), @@ -83,12 +89,13 @@ class _StudyRoomWidgetViewState extends ConsumerState { Widget _freeRooms(AsyncSnapshot snapshot) { if (snapshot.data?.rooms != null) { - final freeRooms = ref.read(studyRoomWidgetViewModel).countAvailableRooms(); - return Text( - "$freeRooms room${freeRooms > 1 ? "s" : ""} free", + final freeRooms = + ref.read(studyRoomWidgetViewModel).countAvailableRooms(); + return Text(context.localizations.nfreeRooms(freeRooms), style: TextStyle(color: freeRooms > 0 ? Colors.green : Colors.red)); } else { - return const Text("no free rooms", style: TextStyle(color: Colors.red)); + return Text(context.localizations.nfreeRooms(0), + style: const TextStyle(color: Colors.red)); } } } diff --git a/lib/placesComponent/views/studyGroups/study_room_group_view.dart b/lib/placesComponent/views/studyGroups/study_room_group_view.dart index 5a05069f..f3de7381 100644 --- a/lib/placesComponent/views/studyGroups/study_room_group_view.dart +++ b/lib/placesComponent/views/studyGroups/study_room_group_view.dart @@ -5,6 +5,7 @@ import 'package:campus_flutter/base/views/error_handling_view.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/placesComponent/views/studyGroups/study_room_row_view.dart'; import 'package:campus_flutter/providers_get_it.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -30,9 +31,9 @@ class StudyRoomGroupView extends ConsumerWidget { builder: (context, snapshot) { if (snapshot.hasData && snapshot.data != null) { final studyRoomGroup = snapshot.data!; - final studyRooms = - ref.read(studyRoomWidgetViewModel).rooms.value!; - final lastFetched = ref.read(studyRoomWidgetViewModel).lastFetched.value; + final studyRooms = ref.read(studyRoomWidgetViewModel).rooms.value!; + final lastFetched = + ref.read(studyRoomWidgetViewModel).lastFetched.value; return RefreshIndicator( onRefresh: () => ref.read(studyRoomWidgetViewModel).fetch(true), child: SingleChildScrollView( @@ -45,8 +46,10 @@ class StudyRoomGroupView extends ConsumerWidget { style: Theme.of(context).textTheme.titleLarge)), //const PaddedDivider(), WidgetFrameView( - title: "Rooms", - subtitle: lastFetched != null ? LastUpdatedText(lastFetched) : null, + title: context.localizations.rooms, + subtitle: lastFetched != null + ? LastUpdatedText(lastFetched) + : null, child: CardWithPadding( child: Column( children: [ diff --git a/lib/placesComponent/views/studyGroups/study_room_row_view.dart b/lib/placesComponent/views/studyGroups/study_room_row_view.dart index 100753e8..e2e87f3e 100644 --- a/lib/placesComponent/views/studyGroups/study_room_row_view.dart +++ b/lib/placesComponent/views/studyGroups/study_room_row_view.dart @@ -1,5 +1,6 @@ import 'package:campus_flutter/base/helpers/icon_text.dart'; import 'package:campus_flutter/placesComponent/model/studyRooms/study_room.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -15,24 +16,29 @@ class StudyRoomRowView extends ConsumerWidget { child: Row( children: [ Expanded( - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(studyRoom.name ?? "Unknown", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: - Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500)), - IconText(iconData: Icons.numbers, label: studyRoom.code ?? "Unkown") - ])), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(studyRoom.name ?? context.localizations.unknown, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .bodyLarge + ?.copyWith(fontWeight: FontWeight.w500)), + IconText( + iconData: Icons.numbers, + label: studyRoom.code ?? context.localizations.unknown) + ])), Expanded( child: Text( - studyRoom.localizedStatus, + studyRoom.localizedStatus(context), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.end, - style: Theme.of(context) - .textTheme - .bodyLarge - ?.copyWith(color: _statusColor(studyRoom.localizedStatus)), + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: _statusColor( + studyRoom.localizedStatus(context), context)), )), //const Padding(padding: EdgeInsets.symmetric(horizontal: 5.0)), //const Icon(Icons.arrow_forward_ios, size: 15) @@ -40,8 +46,8 @@ class StudyRoomRowView extends ConsumerWidget { )); } - Color _statusColor(String status) { - if (status == "Free") { + Color _statusColor(String status, BuildContext context) { + if (status == context.localizations.free) { return Colors.green; } else { return Colors.red; diff --git a/lib/profileComponent/services/profile_service.dart b/lib/profileComponent/services/profile_service.dart index a93c9335..cd10ed40 100644 --- a/lib/profileComponent/services/profile_service.dart +++ b/lib/profileComponent/services/profile_service.dart @@ -9,25 +9,26 @@ import 'package:campus_flutter/providers_get_it.dart'; class ProfileService { static Future<(DateTime?, Profile)> fetchProfile(bool forcedRefresh) async { MainApi mainApi = getIt(); - final response = - await mainApi.makeRequestWithException( - TumOnlineApi(TumOnlineServiceIdentify()), - ProfileData.fromJson, - TumOnlineApiException.fromJson, - forcedRefresh); + final response = await mainApi.makeRequestWithException( + TumOnlineApi(TumOnlineServiceIdentify()), + ProfileData.fromJson, + TumOnlineApiException.fromJson, + forcedRefresh); return (response.saved, response.data.profilesAttribute.profile); } - static Future<(DateTime?, Tuition?)> fetchTuition(bool forcedRefresh, String personGroup, String id) async { + static Future<(DateTime?, Tuition?)> fetchTuition( + bool forcedRefresh, String personGroup, String id) async { MainApi mainApi = getIt(); - final response = - await mainApi.makeRequestWithException( - TumOnlineApi(TumOnlineServiceTuitionStatus()), - TuitionData.fromJson, - TumOnlineApiException.fromJson, - forcedRefresh); + final response = await mainApi.makeRequestWithException( + TumOnlineApi(TumOnlineServiceTuitionStatus()), + TuitionData.fromJson, + TumOnlineApiException.fromJson, + forcedRefresh); return (response.saved, response.data.profilesAttribute.tuition); } -} \ No newline at end of file +} diff --git a/lib/profileComponent/viewModel/profile_viewmodel.dart b/lib/profileComponent/viewModel/profile_viewmodel.dart index fb5d855d..c675c9d6 100644 --- a/lib/profileComponent/viewModel/profile_viewmodel.dart +++ b/lib/profileComponent/viewModel/profile_viewmodel.dart @@ -15,8 +15,10 @@ class ProfileViewModel implements ViewModel { ProfileService.fetchProfile(forcedRefresh).then((response) { lastFetched.add(response.$1); profile.add(response.$2); - ProfileService.fetchTuition(forcedRefresh, response.$2.personGroup ?? "", response.$2.id ?? "") - .then((response) => tuition.add(response.$2), onError: (error) => tuition.addError(error)); + ProfileService.fetchTuition(forcedRefresh, response.$2.personGroup ?? "", + response.$2.id ?? "") + .then((response) => tuition.add(response.$2), + onError: (error) => tuition.addError(error)); }, onError: (error) => profile.addError(error)); } -} \ No newline at end of file +} diff --git a/lib/providers_get_it.dart b/lib/providers_get_it.dart index 13a70d85..9f35515b 100644 --- a/lib/providers_get_it.dart +++ b/lib/providers_get_it.dart @@ -1,3 +1,5 @@ +import 'dart:io'; +import 'dart:ui'; import 'package:campus_flutter/calendarComponent/model/calendar_event.dart'; import 'package:campus_flutter/calendarComponent/viewModels/calendar_viewmodel.dart'; import 'package:campus_flutter/departuresComponent/viewModel/departures_viewmodel.dart'; @@ -25,6 +27,16 @@ final selectedLecture = StateProvider((ref) => null); final selectedEvent = StateProvider((ref) => null); final selectedProfile = StateProvider((ref) => null); final useWebView = StateProvider((ref) => true); +final locale = StateProvider((ref) => _getDeviceLocale()); + +Locale _getDeviceLocale() { + final deviceLocal = Platform.localeName; + if (deviceLocal.contains("de")) { + return const Locale("de"); + } else { + return const Locale("en"); + } +} /// viewModels for RiverPod - state is uninitialized at first final loginViewModel = Provider((ref) => LoginViewModel()); @@ -59,4 +71,4 @@ final lectureDetailsViewModel = Provider((ref) { final gradeViewModel = Provider((ref) => GradeViewModel()); final calendarViewModel = Provider((ref) => CalendarViewModel()); final homeSplitViewModel = Provider((ref) => SplitViewViewModel()); -final lectureSplitViewModel = Provider((ref) => SplitViewViewModel()); \ No newline at end of file +final lectureSplitViewModel = Provider((ref) => SplitViewViewModel()); diff --git a/lib/routes.dart b/lib/routes.dart index 77aca928..a3ef7787 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -3,4 +3,4 @@ const confirm = '/confirm'; const grades = '/grades'; const lectures = '/lectures'; const calendar = '/calendar'; -const places = '/places'; \ No newline at end of file +const places = '/places'; diff --git a/lib/settingsComponent/views/settings_view.dart b/lib/settingsComponent/views/settings_view.dart index 4c261007..75bf5802 100644 --- a/lib/settingsComponent/views/settings_view.dart +++ b/lib/settingsComponent/views/settings_view.dart @@ -1,15 +1,18 @@ import 'dart:io'; +import 'package:campus_flutter/base/extensions/locale+fullname.dart'; import 'package:campus_flutter/base/helpers/hyperlink_text.dart'; import 'package:campus_flutter/base/helpers/padded_divider.dart'; import 'package:campus_flutter/homeComponent/widgetComponent/views/widget_frame_view.dart'; import 'package:campus_flutter/loginComponent/viewModels/login_viewmodel.dart'; import 'package:campus_flutter/loginComponent/views/permission_check_view.dart'; import 'package:campus_flutter/providers_get_it.dart'; +import 'package:campus_flutter/theme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class SettingsView extends ConsumerWidget { const SettingsView({super.key}); @@ -19,11 +22,11 @@ class SettingsView extends ConsumerWidget { return Scaffold( appBar: AppBar( leading: const BackButton(), - title: const Text("Settings"), + title: Text(context.localizations.settings), ), body: ListView(children: [ _generalSettings(context, ref), - _contact(ref), + _contact(context, ref), _authentication(context, ref), _versionNumber() ])); @@ -31,61 +34,78 @@ class SettingsView extends ConsumerWidget { Widget _generalSettings(BuildContext context, WidgetRef ref) { return WidgetFrameView( - title: "General Settings", - child: Column(children: [ + title: context.localizations.generalSettings, + child: Card( + child: Column(children: [ _tokenPermission(context), - if (!kIsWeb && Platform.isIOS) _useWebView(context, ref) - ])); + const PaddedDivider(height: 0), + _localeSelection(context, ref), + if (!kIsWeb && Platform.isIOS) const PaddedDivider(height: 0), + if (!kIsWeb && Platform.isIOS) _useWebView(context, ref), + ]))); } Widget _tokenPermission(BuildContext context) { - return GestureDetector( - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => - const PermissionCheckView(isSettingsView: true))), - child: Card( - child: ListTile( - dense: true, - leading: - Icon(Icons.key, size: 20, color: Theme.of(context).primaryColor), - title: Text("Token Permissions", - style: Theme.of(context).textTheme.bodyMedium), - trailing: const Icon(Icons.arrow_forward_ios, size: 15), - ))); + return ListTile( + leading: Icon(Icons.key, size: 20, color: Theme.of(context).primaryColor), + title: Text(context.localizations.tokenPermissions, + style: Theme.of(context).textTheme.bodyMedium), + trailing: const Icon(Icons.arrow_forward_ios, size: 15), + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => + const PermissionCheckView(isSettingsView: true))), + ); + } + + Widget _localeSelection(BuildContext context, WidgetRef ref) { + return ListTile( + leading: Icon(Icons.language, + size: 20, color: Theme.of(context).primaryColor), + title: Text(context.localizations.language, + style: Theme.of(context).textTheme.bodyMedium), + trailing: DropdownButton( + onChanged: (Locale? newLocale) { + if (newLocale != null) { + ref.read(locale.notifier).state = newLocale; + } + }, + value: ref.watch(locale), + items: AppLocalizations.supportedLocales + .map((e) => DropdownMenuItem(value: e, child: Text(e.fullName()))) + .toList(), + )); } Widget _useWebView(BuildContext context, WidgetRef ref) { - return Card( - child: ListTile( - dense: true, - title: - Text("Use Web View", style: Theme.of(context).textTheme.bodyMedium), + return ListTile( + title: Text(context.localizations.useWebView, + style: Theme.of(context).textTheme.bodyMedium), trailing: Switch( value: ref.watch(useWebView), onChanged: (showWebView) { ref.read(useWebView.notifier).state = showWebView; }), - )); + ); } - Widget _contact(WidgetRef ref) { + Widget _contact(BuildContext context, WidgetRef ref) { return WidgetFrameView( - title: "Contact Us", + title: context.localizations.contactUs, child: Card( child: Column( children: [ - const ListTile( + ListTile( dense: true, title: HyperLinkText( link: "https://testflight.apple.com/join/4Ddi6f2f", - label: "Become a Beta Tester"), + label: context.localizations.becomeABetaTester), ), const PaddedDivider(height: 0), - const ListTile( + ListTile( dense: true, title: HyperLinkText( link: "https://github.com/TUM-Dev", - label: "TUM Dev on GitHub"), + label: context.localizations.usOnGitHub), ), const PaddedDivider(height: 0), const ListTile( @@ -123,11 +143,11 @@ class SettingsView extends ConsumerWidget { child: ListTile( dense: true, title: login != Credentials.tumId - ? Text("Login", + ? Text(context.localizations.login, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.green, fontWeight: FontWeight.w500), textAlign: TextAlign.center) - : Text("Logout", + : Text(context.localizations.logout, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.red, fontWeight: FontWeight.w500), textAlign: TextAlign.center), @@ -140,9 +160,10 @@ class SettingsView extends ConsumerWidget { future: PackageInfo.fromPlatform(), builder: (context, snapshot) { if (snapshot.hasData) { - return Text("Version ${snapshot.data!.version}"); + return Text(context.localizations + .versionNumber(snapshot.data!.version)); } else { - return const Text("Version -.-.-"); + return Text(context.localizations.versionNumber("-.-.-")); } })); } diff --git a/lib/theme.dart b/lib/theme.dart index 201ba435..d3c76de2 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; const Color _primaryLightColor = Color(0xff0064BC); const Color _primaryDarkColor = Color(0xff3070B3); @@ -11,6 +12,10 @@ const Color _darkGray = Color(0xff555555); const Color _almostBlack = Color(0xff1a1c1e); const Color _almostWhite = Color(0xffe3e2e6); +extension Localization on BuildContext { + AppLocalizations get localizations => AppLocalizations.of(this)!; +} + /// light theme ThemeData lightTheme(BuildContext context) { return ThemeData( @@ -126,7 +131,8 @@ ThemeData lightTheme(BuildContext context) { .textTheme .labelMedium ?.copyWith( - /*color: _navigationIconGrayLight, */fontWeight: FontWeight.w500), + /*color: _navigationIconGrayLight, */ fontWeight: + FontWeight.w500), selectedIconTheme: const IconThemeData(color: _primaryLightColor), /*unselectedIconTheme: const IconThemeData(color: _navigationIconGrayLight),*/ @@ -143,9 +149,8 @@ ThemeData lightTheme(BuildContext context) { surfaceTintColor: Colors.transparent), /// style snackbar - snackBarTheme: const SnackBarThemeData( - backgroundColor: Colors.redAccent - )); + snackBarTheme: + const SnackBarThemeData(backgroundColor: Colors.redAccent)); } /// dark theme @@ -259,10 +264,10 @@ ThemeData darkTheme(BuildContext context) { .textTheme .labelMedium ?.copyWith( - color: _navigationIconGrayLight, fontWeight: FontWeight.w500), + color: _navigationIconGrayLight, fontWeight: FontWeight.w500), selectedIconTheme: const IconThemeData(color: _primaryLightColor), unselectedIconTheme: - const IconThemeData(color: _navigationIconGrayLight), + const IconThemeData(color: _navigationIconGrayLight), //indicatorColor: Colors.transparent, useIndicator: false, backgroundColor: _darkBackground, @@ -276,7 +281,6 @@ ThemeData darkTheme(BuildContext context) { surfaceTintColor: Colors.transparent), /// style snackbar - snackBarTheme: const SnackBarThemeData( - backgroundColor: Colors.redAccent - )); + snackBarTheme: + const SnackBarThemeData(backgroundColor: Colors.redAccent)); } diff --git a/pubspec.lock b/pubspec.lock index f66dfa1e..85042755 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -406,6 +406,11 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_native_splash: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8ed07cef..0895dfc2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: geolocator: ^10.0.1 hive: ^2.2.3 http: ^0.13.5 - intl: ^0.18.1 + intl: any json_annotation: ^4.8.0 json_serializable: ^6.6.1 package_info_plus: ^4.0.2 @@ -46,6 +46,8 @@ dependencies: timeago: ^3.4.0 webview_flutter: ^4.2.2 flutter_launcher_icons: ^0.13.1 + flutter_localizations: + sdk: flutter dependency_overrides: xml2json: @@ -63,6 +65,7 @@ dev_dependencies: dart_code_metrics: ^5.7.6 flutter: + generate: true uses-material-design: true assets: - assets/