From c9500a2690106d5dd3b03b006b2c9b5d931eeb97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ja=C5=82ocha?= <76820915+mikolaj-jalocha@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:07:13 +0100 Subject: [PATCH] feat(digital-guide): add rooms content (#531) * feat(digital-guide): add data layer for rooms section * feat(digital-guide): add presentation layer * feat(digital-guide): navigation * feat(digital-guide): localization * feat(digital-guide): docs * feat(digital-guide): apply pr's fixes * feat(digital-guide): final touch * feat(digital-guide): final touch v2 --- .../digital_guide_features_section.dart | 7 +- lib/features/digital_guide_view/readme.md | 5 +- .../rooms/data/models/digital_guide_room.dart | 45 ++++++++ .../data/repository/rooms_repository.dart | 26 +++++ .../digital_guide_room_detail_view.dart | 109 ++++++++++++++++++ ...al_guide_rooms_expansion_tile_content.dart | 64 ++++++++++ lib/features/navigator/app_router.dart | 6 + .../navigator/utils/navigation_commands.dart | 7 +- lib/l10n/app_pl.arb | 8 +- 9 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 lib/features/digital_guide_view/tabs/rooms/data/models/digital_guide_room.dart create mode 100644 lib/features/digital_guide_view/tabs/rooms/data/repository/rooms_repository.dart create mode 100644 lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_room_detail_view.dart create mode 100644 lib/features/digital_guide_view/tabs/rooms/presentation/digital_guide_rooms_expansion_tile_content.dart 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 7a06d30d..7a92f0e9 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 @@ -11,6 +11,7 @@ import "../../tabs/adapted_toilets/presentation/adapted_toilets_expansion_tile_c 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/rooms/presentation/digital_guide_rooms_expansion_tile_content.dart"; import "../../tabs/surrounding/presentation/surroundings_expansion_tile_content.dart"; typedef TileContent = ({String title, List content}); @@ -82,7 +83,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 e8b7f135..26f1b80c 100644 --- a/lib/features/digital_guide_view/readme.md +++ b/lib/features/digital_guide_view/readme.md @@ -9,9 +9,12 @@ # 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 * /surrounding/data/repository/surrounding_repository.dart * DIGITAL_GUIDE_URL/surroundings/{id} +3) 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 25aa4eab..7d59cbf1 100644 --- a/lib/features/navigator/app_router.dart +++ b/lib/features/navigator/app_router.dart @@ -10,6 +10,8 @@ import "../departments_view/departments_view.dart"; import "../digital_guide_view/presentation/digital_guide_view.dart"; import "../digital_guide_view/tabs/adapted_toilets/data/models/adapted_toilet.dart"; import "../digital_guide_view/tabs/adapted_toilets/presentation/adapted_toilet_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"; @@ -21,6 +23,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 { @@ -108,6 +111,9 @@ class AppRouter extends RootStackRouter { path: "/aboutUs", page: AboutUsRoute.page, ), + AutoRoute( + page: DigitalGuideRoomDetailRoute.page, + ), ]; } diff --git a/lib/features/navigator/utils/navigation_commands.dart b/lib/features/navigator/utils/navigation_commands.dart index 89cb0cf1..d32c7f5a 100644 --- a/lib/features/navigator/utils/navigation_commands.dart +++ b/lib/features/navigator/utils/navigation_commands.dart @@ -4,12 +4,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/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); @@ -85,4 +86,8 @@ extension NavigationX on WidgetRef { Future navigateAdaptedToiletDetails(AdaptedToilet adaptedToilet) async { await _router.push(AdaptedToiletDetailRoute(adaptedToilet: adaptedToilet)); } + + 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 dd8e105a..63809452 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. ", @@ -283,5 +283,9 @@ "strategicBadgeTooltip": "Koło Strategiczne PWr", "strategic_club_abbr": "KS", "emptySection": "Brak członków zespołu w tej wersji", - "digital_guide_offline" : "Cyfrowego Przewodnika" + "digital_guide_offline" : "Cyfrowego Przewodnika", + "platforms" : "Podesty", + "stairs" : "Schody", + "key_information" : "Najważniejsze informacje", + "working_hours" : "Godziny otwarcia" }