diff --git a/assets/translations/de.json b/assets/translations/de.json index 6b8c9d67..0ce18b15 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -103,6 +103,8 @@ "occupiedUntil": "Belegt bis {}", "rooms": "Räume", "roomDetails": "Raumdetails", + "openInNavigaTum": "In NavigaTUM öffnen", + "openRoomCalendar": "Raumkalender öffnen", "building": "Gebäude", "nFreeRooms": { "zero": "Keine freien Räume", diff --git a/assets/translations/en.json b/assets/translations/en.json index c1c49833..6ba9296c 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -103,6 +103,8 @@ "occupiedUntil": "Occupied until {}", "rooms": "Rooms", "roomDetails": "Room Details", + "openInNavigaTum": "Open in NavigaTUM", + "openRoomCalendar": "Open room calendar", "building": "Building", "nFreeRooms": { "zero": "No free rooms", diff --git a/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.dart b/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.dart index bfb5c1c5..e4aa031f 100644 --- a/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.dart +++ b/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.dart @@ -7,8 +7,10 @@ part 'navigatum_navigation_additional_properties.g.dart'; class NavigaTumNavigationAdditionalProperties { @JsonKey(name: "computed") final List properties; + @JsonKey(name: "calendar_url") + final String? calendarUrl; - NavigaTumNavigationAdditionalProperties(this.properties); + NavigaTumNavigationAdditionalProperties(this.properties, this.calendarUrl); factory NavigaTumNavigationAdditionalProperties.fromJson( Map json, diff --git a/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.g.dart b/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.g.dart index aee2fc0e..c3695b74 100644 --- a/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.g.dart +++ b/lib/navigaTumComponent/model/details/navigatum_navigation_additional_properties.g.dart @@ -15,8 +15,12 @@ _$NavigaTumNavigationAdditionalPropertiesFromJson(Map json) => NavigaTumNavigationProperty.fromJson(e as Map), ) .toList(), + json['calendar_url'] as String?, ); Map _$NavigaTumNavigationAdditionalPropertiesToJson( NavigaTumNavigationAdditionalProperties instance, -) => {'computed': instance.properties}; +) => { + 'computed': instance.properties, + 'calendar_url': instance.calendarUrl, +}; diff --git a/lib/navigaTumComponent/model/navigatum_navigation_details.dart b/lib/navigaTumComponent/model/navigatum_navigation_details.dart index 5cb76f3c..e7757119 100644 --- a/lib/navigaTumComponent/model/navigatum_navigation_details.dart +++ b/lib/navigaTumComponent/model/navigatum_navigation_details.dart @@ -23,6 +23,19 @@ class NavigaTumNavigationDetails { bool get hasCoordinates => coordinates.latitude != null && coordinates.longitude != null; + Uri get navigaTumUri => + Uri(scheme: "https", host: "nav.tum.de", pathSegments: ["room", id]); + + Uri? get calendarUri { + final uri = additionalProperties.calendarUrl != null + ? Uri.tryParse(additionalProperties.calendarUrl!) + : null; + if (uri == null || uri.scheme != "https" || uri.host != "campus.tum.de") { + return null; + } + return uri; + } + NavigaTumNavigationDetails( this.id, this.name, diff --git a/lib/navigaTumComponent/views/navigatum_room_view.dart b/lib/navigaTumComponent/views/navigatum_room_view.dart index 47d95f31..45a14fdd 100644 --- a/lib/navigaTumComponent/views/navigatum_room_view.dart +++ b/lib/navigaTumComponent/views/navigatum_room_view.dart @@ -3,6 +3,7 @@ import 'package:campus_flutter/base/util/custom_back_button.dart'; import 'package:campus_flutter/base/util/delayed_loading_indicator.dart'; import 'package:campus_flutter/base/errorHandling/error_handling_router.dart'; import 'package:campus_flutter/base/util/map_launcher.dart'; +import 'package:campus_flutter/base/util/url_launcher.dart'; import 'package:campus_flutter/navigaTumComponent/model/navigatum_navigation_details.dart'; import 'package:campus_flutter/navigaTumComponent/viewModels/navigatum_details_viewmodel.dart'; import 'package:campus_flutter/navigaTumComponent/views/navigatum_room_details_view.dart'; @@ -29,8 +30,8 @@ class NavigaTumRoomScaffold extends ConsumerWidget { StreamBuilder( stream: ref.watch(navigaTumDetailsViewModel(id)).details, builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data!.hasCoordinates) { - return _directionsButton(snapshot.data!, context); + if (snapshot.hasData) { + return _actionButtons(snapshot.data!, context, ref); } else { return const SizedBox.shrink(); } @@ -42,6 +43,30 @@ class NavigaTumRoomScaffold extends ConsumerWidget { ); } + Widget _actionButtons( + NavigaTumNavigationDetails details, + BuildContext context, + WidgetRef ref, + ) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () => UrlLauncher.url(details.navigaTumUri, ref), + tooltip: context.tr("openInNavigaTum"), + icon: Icon(Icons.open_in_new, color: context.theme.primaryColor), + ), + if (details.calendarUri != null) + IconButton( + onPressed: () => UrlLauncher.url(details.calendarUri!, ref), + tooltip: context.tr("openRoomCalendar"), + icon: Icon(Icons.calendar_month, color: context.theme.primaryColor), + ), + if (details.hasCoordinates) _directionsButton(details, context), + ], + ); + } + Widget _directionsButton( NavigaTumNavigationDetails details, BuildContext context, diff --git a/test/navigaTumApi/navigatum_test.dart b/test/navigaTumApi/navigatum_test.dart index 0bfd319f..8430f90e 100644 --- a/test/navigaTumApi/navigatum_test.dart +++ b/test/navigaTumApi/navigatum_test.dart @@ -115,7 +115,8 @@ void main() { "computed": [ {"name": "Seats", "text": "200"}, {"name": "Building", "text": "MI"} - ] + ], + "calendar_url": "https://campus.tum.de/tumonline/tvKalender.wSicht?cOrg=19691&cRes=12543&cReadonly=J" }, "coords": { "lat": 48.26244, @@ -163,6 +164,12 @@ void main() { expect(details.additionalProperties.properties.length, 2); expect(details.additionalProperties.properties[0].name, 'Seats'); expect(details.additionalProperties.properties[0].text, '200'); + expect( + details.additionalProperties.calendarUrl, + 'https://campus.tum.de/tumonline/tvKalender.wSicht?cOrg=19691&cRes=12543&cReadonly=J', + ); + expect(details.calendarUri?.host, 'campus.tum.de'); + expect(details.navigaTumUri.toString(), 'https://nav.tum.de/room/5606.EG.001'); // Maps expect(details.maps.defaultMapId, 'rf142'); @@ -190,5 +197,44 @@ void main() { expect(details.hasCoordinates, isFalse); }); + + test('navigaTumUri encodes the room id as a single path segment', () { + final json = j(''' +{ + "id": "5500/01?A", + "name": "Test Room", + "parent_names": ["Garching"], + "type": "room", + "type_common_name": "Room", + "props": {"computed": []}, + "coords": {}, + "maps": {"default": "rf000"} +} +'''); + final details = NavigaTumNavigationDetails.fromJson(json); + + expect(details.navigaTumUri.toString(), 'https://nav.tum.de/room/5500%2F01%3FA'); + }); + + test('calendarUri rejects untrusted schemes', () { + final json = j(''' +{ + "id": "5606.EG.001", + "name": "Test Room", + "parent_names": ["Garching"], + "type": "room", + "type_common_name": "Room", + "props": { + "computed": [], + "calendar_url": "javascript:alert(1)" + }, + "coords": {}, + "maps": {"default": "rf000"} +} +'''); + final details = NavigaTumNavigationDetails.fromJson(json); + + expect(details.calendarUri, isNull); + }); }); }