diff --git a/lib/features/digital_guide_view/presentation/widgets/digital_guide_features_section.dart b/lib/features/digital_guide_view/presentation/widgets/digital_guide_features_section.dart index 420953ac..ce6af862 100644 --- a/lib/features/digital_guide_view/presentation/widgets/digital_guide_features_section.dart +++ b/lib/features/digital_guide_view/presentation/widgets/digital_guide_features_section.dart @@ -12,6 +12,7 @@ import "../../tabs/amenities/presentation/amenities_expansion_tile_content.dart" import "../../tabs/evacuation/evacuation_widget.dart"; import "../../tabs/localization/presentation/localization_expansion_tile_content.dart"; import "../../tabs/micronavigation/presentation/micronavigation_expansion_tile_content.dart"; +import "../../tabs/rooms/presentation/digital_guide_rooms_expansion_tile_content.dart"; import "../../tabs/surrounding/presentation/surroundings_expansion_tile_content.dart"; typedef TileContent = ({String title, List content}); @@ -87,7 +88,11 @@ class DigitalGuideFeaturesSection extends ConsumerWidget { ), ( title: context.localize.room_information, - content: [LocalizationExpansionTileContent()], + content: [ + DigitalGuideRoomExpansionTileContent( + digitalGuideResponse: digitalGuideData, + ), + ], ), ( title: context.localize.evacuation, diff --git a/lib/features/digital_guide_view/readme.md b/lib/features/digital_guide_view/readme.md index ddc93adf..69458915 100644 --- a/lib/features/digital_guide_view/readme.md +++ b/lib/features/digital_guide_view/readme.md @@ -10,13 +10,18 @@ # Used endpoints 1) Building data and image - * /general_info/data/repository/digita_guide_repository.dart + * /general_info/data/repository/digital_guide_repository.dart * DIGITAL_GUIDE_URL/buildings/{id} * DIGITAL_GUIDE_URL/images/{id} 2) Surroundings data * tabs/surrounding/data/repository/surrounding_repository.dart * DIGITAL_GUIDE_URL/surroundings/{id} + 3) Micronavigation data * /tabs/micronavigation/data/repository/micronavigation_repository.dart - * DIGITAL_GUIDE_URL_MICRONAVIGATION/beaconplus/?location={external_id} - * Note that endpoint above is different than usual one and uses external_id from digital_guide_response. Additionaly it is unprotected, so for now it is not being added to .env \ No newline at end of file + * DIGITAL_GUIDE_ADDONS_URL/beaconplus/?location={external_id} + +4) Rooms data + * /rooms/data/repository/rooms_repository.dart + * DIGITAL_GUIDE_URL/rooms/{id} + diff --git a/lib/features/digital_guide_view/tabs/rooms/data/models/digital_guide_room.dart b/lib/features/digital_guide_view/tabs/rooms/data/models/digital_guide_room.dart new file mode 100644 index 00000000..eecd2574 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/rooms/data/models/digital_guide_room.dart @@ -0,0 +1,45 @@ +import "package:freezed_annotation/freezed_annotation.dart"; + +part "digital_guide_room.freezed.dart"; + +part "digital_guide_room.g.dart"; + +@freezed +class DigitalGuideRoom with _$DigitalGuideRoom { + const factory DigitalGuideRoom({ + required int id, + required DigitalGuideTranslationsRoom translations, + @JsonKey(name: "images") required List? imagesIds, + }) = _DigitalGuideRoom; + + factory DigitalGuideRoom.fromJson(Map json) => + _$DigitalGuideRoomFromJson(json); +} + +@freezed +class DigitalGuideTranslationsRoom with _$DigitalGuideTranslationsRoom { + const factory DigitalGuideTranslationsRoom({ + required DigitalGuideTranslationRoom pl, + }) = _DigitalGuideTranslationsRoom; + + factory DigitalGuideTranslationsRoom.fromJson(Map json) => + _$DigitalGuideTranslationsRoomFromJson(json); +} + +@freezed +class DigitalGuideTranslationRoom with _$DigitalGuideTranslationRoom { + @JsonSerializable(fieldRename: FieldRename.snake) + const factory DigitalGuideTranslationRoom({ + required String name, + required String roomPurpose, + required String location, + required String workingDaysAndHours, + required String areEntrancesComment, + required String isOneLevelFloorComment, + required String arePlacesForWheelchairsComment, + required String comment, + }) = _DigitalGuideTranslationRoom; + + factory DigitalGuideTranslationRoom.fromJson(Map json) => + _$DigitalGuideTranslationRoomFromJson(json); +} diff --git a/lib/features/digital_guide_view/tabs/rooms/data/repository/rooms_repository.dart b/lib/features/digital_guide_view/tabs/rooms/data/repository/rooms_repository.dart new file mode 100644 index 00000000..3b8404cb --- /dev/null +++ b/lib/features/digital_guide_view/tabs/rooms/data/repository/rooms_repository.dart @@ -0,0 +1,26 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:riverpod_annotation/riverpod_annotation.dart"; + +import "../../../../data/api/digital_guide_get_and_cache.dart"; +import "../../../../data/models/digital_guide_response.dart"; +import "../models/digital_guide_room.dart"; + +part "rooms_repository.g.dart"; + +@riverpod +Future> roomsRepository( + Ref ref, + DigitalGuideResponse building, +) async { + final String endpoint = "rooms/?building=${building.id}"; + + return ref.getAndCacheDataFromDigitalGuide( + endpoint, + (List json) => json + .whereType>() + .map(DigitalGuideRoom.fromJson) + .toIList(), + onRetry: () => ref.invalidateSelf(), + ); +} diff --git a/lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_room_detail_view.dart b/lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_room_detail_view.dart new file mode 100644 index 00000000..8f64328f --- /dev/null +++ b/lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_room_detail_view.dart @@ -0,0 +1,109 @@ +import "package:auto_route/annotations.dart"; +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; + +import "../../../../../config/ui_config.dart"; +import "../../../../../theme/app_theme.dart"; +import "../../../../../utils/context_extensions.dart"; +import "../../../../../utils/ilist_nonempty.dart"; +import "../../../../../widgets/detail_views/detail_view_app_bar.dart"; +import "../../../presentation/widgets/accessibility_button.dart"; +import "../../../presentation/widgets/bullet_list.dart"; +import "../../../presentation/widgets/digital_guide_nav_link.dart"; +import "../../../presentation/widgets/digital_guide_photo_row.dart"; +import "../data/models/digital_guide_room.dart"; + +@RoutePage() +class DigitalGuideRoomDetailView extends ConsumerWidget { + const DigitalGuideRoomDetailView({required this.room}); + + final DigitalGuideRoom room; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final roomInformation = room.translations.pl; + final widgets = [ + Text( + roomInformation.name, + style: context.textTheme.headline.copyWith(fontSize: 18), + ), + const SizedBox(height: DigitalGuideConfig.heightTiny), + Text( + roomInformation.roomPurpose, + style: context.textTheme.headline + .copyWith(fontSize: 12, fontWeight: FontWeight.normal), + ), + if (roomInformation.workingDaysAndHours.isNotEmpty) + const SizedBox(height: DigitalGuideConfig.heightMedium), + if (roomInformation.workingDaysAndHours.isNotEmpty) + Text( + "${context.localize.working_hours}:", + style: context.textTheme.headline, + ), + if (roomInformation.workingDaysAndHours.isNotEmpty) + const SizedBox(height: DigitalGuideConfig.heightSmall), + Text( + roomInformation.workingDaysAndHours, + style: context.textTheme.body.copyWith(fontSize: 16), + ), + if (roomInformation.workingDaysAndHours.isNotEmpty) + const SizedBox(height: DigitalGuideConfig.heightMedium), + Text( + context.localize.key_information, + style: context.textTheme.headline, + ), + const SizedBox(height: DigitalGuideConfig.heightSmall), + BulletList( + items: [ + roomInformation.location, + roomInformation.comment, + roomInformation.areEntrancesComment, + roomInformation.isOneLevelFloorComment, + roomInformation.arePlacesForWheelchairsComment, + roomInformation.isOneLevelFloorComment, + ].toIList(), + ), + if (room.imagesIds != null && room.imagesIds!.isNotEmpty) + const SizedBox( + height: DigitalGuideConfig.heightMedium, + ), + DigitalGuidePhotoRow(imagesIDs: room.imagesIds.toIList()), + const SizedBox( + height: DigitalGuideConfig.heightMedium, + ), + DigitalGuideNavLink( + onTap: () {}, + text: context.localize.doors, + ), + const SizedBox( + height: DigitalGuideConfig.heightMedium, + ), + DigitalGuideNavLink( + onTap: () {}, + text: context.localize.platforms, + ), + const SizedBox( + height: DigitalGuideConfig.heightMedium, + ), + DigitalGuideNavLink( + onTap: () {}, + text: context.localize.stairs, + ), + ]; + return Scaffold( + appBar: DetailViewAppBar( + actions: [AccessibilityButton()], + ), + body: Padding( + padding: const EdgeInsets.all(DigitalGuideConfig.paddingMedium), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: widgets.length, + shrinkWrap: true, + itemBuilder: (context, index) => widgets[index], + ), + ), + ); + } +} diff --git a/lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_rooms_expansion_tile_content.dart b/lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_rooms_expansion_tile_content.dart new file mode 100644 index 00000000..809d1a69 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_rooms_expansion_tile_content.dart @@ -0,0 +1,64 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; + +import "../../../../../config/ui_config.dart"; +import "../../../../../widgets/my_error_widget.dart"; +import "../../../../navigator/utils/navigation_commands.dart"; +import "../../../data/models/digital_guide_response.dart"; +import "../../../presentation/widgets/digital_guide_nav_link.dart"; +import "../data/models/digital_guide_room.dart"; +import "../data/repository/rooms_repository.dart"; + +class DigitalGuideRoomExpansionTileContent extends ConsumerWidget { + const DigitalGuideRoomExpansionTileContent({ + required this.digitalGuideResponse, + }); + + final DigitalGuideResponse digitalGuideResponse; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final roomsInBuilding = + ref.watch(roomsRepositoryProvider(digitalGuideResponse)); + return roomsInBuilding.when( + data: (data) => _DigitalGuideRoomExpansionTileContent(rooms: data), + error: (error, _) => MyErrorWidget(error), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ); + } +} + +class _DigitalGuideRoomExpansionTileContent extends ConsumerWidget { + const _DigitalGuideRoomExpansionTileContent({required this.rooms}); + + final IList rooms; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: DigitalGuideConfig.paddingMedium, + vertical: DigitalGuideConfig.paddingMedium, + ), + child: ListView.separated( + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (context, index) => const SizedBox( + height: DigitalGuideConfig.heightMedium, + ), + itemCount: rooms.length, + shrinkWrap: true, + itemBuilder: (context, index) => DigitalGuideNavLink( + onTap: () async { + await ref.navigateRoomDetails( + rooms[index], + ); + }, + text: rooms[index].translations.pl.name, + ), + ), + ); + } +} diff --git a/lib/features/navigator/app_router.dart b/lib/features/navigator/app_router.dart index 3aa7da10..ce4f28bc 100644 --- a/lib/features/navigator/app_router.dart +++ b/lib/features/navigator/app_router.dart @@ -12,6 +12,8 @@ import "../digital_guide_view/tabs/adapted_toilets/data/models/adapted_toilet.da import "../digital_guide_view/tabs/adapted_toilets/presentation/adapted_toilet_detail_view.dart"; import "../digital_guide_view/tabs/micronavigation/data/models/micronavigation_response.dart"; import "../digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart"; +import "../digital_guide_view/tabs/rooms/data/models/digital_guide_room.dart"; +import "../digital_guide_view/tabs/rooms/presentation/digital_guide_room_detail_view.dart"; import "../guide_detail_view/guide_detail_view.dart"; import "../guide_view/guide_view.dart"; import "../home_view/home_view.dart"; @@ -23,6 +25,7 @@ import "../sks-menu/presentation/sks_menu_screen.dart"; import "root_view.dart"; part "app_router.g.dart"; + part "app_router.gr.dart"; class _NoTransitionRoute extends CustomRoute { @@ -114,6 +117,9 @@ class AppRouter extends RootStackRouter { path: "/digital-guide/:id/micronavigationDetails", page: MicronavigationDetailRoute.page, ), + AutoRoute( + page: DigitalGuideRoomDetailRoute.page, + ), ]; } diff --git a/lib/features/navigator/utils/navigation_commands.dart b/lib/features/navigator/utils/navigation_commands.dart index 297c4c74..c638fb78 100644 --- a/lib/features/navigator/utils/navigation_commands.dart +++ b/lib/features/navigator/utils/navigation_commands.dart @@ -5,12 +5,13 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "../../buildings_view/model/building_model.dart"; import "../../digital_guide_view/tabs/adapted_toilets/data/models/adapted_toilet.dart"; import "../../digital_guide_view/tabs/micronavigation/data/models/micronavigation_response.dart"; +import "../../digital_guide_view/tabs/rooms/data/models/digital_guide_room.dart"; import "../../parkings_view/models/parking.dart"; import "../app_router.dart"; import "../navigation_controller.dart"; /// just a one place to gather implementation details of navigation flow -/// - for easy maintainance +/// - for easy maintenance extension NavigationX on WidgetRef { NavigationController get _router => read(navigationControllerProvider.notifier); @@ -96,4 +97,8 @@ extension NavigationX on WidgetRef { ), ); } + + Future navigateRoomDetails(DigitalGuideRoom room) async { + await _router.push(DigitalGuideRoomDetailRoute(room: room)); + } } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 1934f8b4..2872a68a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -271,7 +271,7 @@ "people_blind" : "niewidomych", "people_with_motor_disability" : "posiadających dysfunkcje ruchu", "people_with_high_sensory_sensitivity" : "o wysokiej wrażliwości sensorycznej", - "see_all_photos" : "Zobacz {photos, plural, =0{{photos} zdjęć} =1{{photos} zdjęcie} few{wszystkie {photos} zdjęcia} other{wszystkie {photos} zjęć}}", + "see_all_photos" : "Zobacz {photos, plural, =0{{photos} zdjęć} =1{{photos} zdjęcie} few{wszystkie {photos} zdjęcia} other{wszystkie {photos} zdjęć}}", "are_dangerous_elements_comment_prefix" : "W otoczeniu budynku znajdują się następujące obiekty, które mogą stanowić zagrożenie: {text}", "are_high_curbs_at_parking_space_for_pwd" : "Przy miejscach postojowych dla osób z niepełnosprawnościami {value, select, false{nie} other{}} znajduje się wysoki krawężnik. ", "is_lit" : "Otoczenie budynku {value, select, false{nie} other{}} jest oświetlone po zmroku. ", @@ -287,6 +287,9 @@ "communique" : "Komunikat", "audio_message" : "Komunikat Dźwiękowy", "audio_message_comment" : "Sygnał znacznika może różnić się tempem i brzmieniem", - "digital_guide" : "Cyfrowy Przewodnik" - + "digital_guide" : "Cyfrowy Przewodnik", + "platforms" : "Podesty", + "stairs" : "Schody", + "key_information" : "Najważniejsze informacje", + "working_hours" : "Godziny otwarcia" }