From 7a46f524abfe11aa93ac068a55b29af5423d4f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliwier=20Dygda=C5=82owicz?= <70859223+thesun901@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:44:48 +0100 Subject: [PATCH 1/5] feat(digital guide): add micronavigation content (#532) * feat: add micronavigation functionality add micronavigation functionalities, but still requires to do some style changes * chore: remove unused model fields * refactor: remove magic numbers remove magic numbers change name micronavigation_view_details.dart to micronavigation_detail_view.dart fix bug that occured after rebasing branch * chore: add micronavigation link to env * fix: change example.env for lint checking * feat: add micronavigation functionality add micronavigation functionalities, but still requires to do some style changes * chore: remove unused model fields * refactor: remove magic numbers remove magic numbers change name micronavigation_view_details.dart to micronavigation_detail_view.dart fix bug that occured after rebasing branch * chore: add micronavigation link to env * fix: change example.env for lint checking * chore: rebased micronavigation branch * refactor: remove unused response fields * chore: formatting fixes * chore: update example.env * chore: apply formatting fixes after rebase * chore: apply minor enhancements * chore: minor debugs --- README.md | 1 + example.env | 3 +- lib/config/env.dart | 2 + lib/config/ui_config.dart | 3 + .../data/models/digital_guide_response.dart | 1 + .../digital_guide_features_section.dart | 7 +- lib/features/digital_guide_view/readme.md | 11 +- .../data/models/micronavigation_response.dart | 50 +++++++++ .../micronavigation_repository.dart | 37 +++++++ .../micronavigation_detail_view.dart | 77 +++++++++++++ ...icronavigation_expansion_tile_content.dart | 75 +++++++++++++ .../presentation/widgets/my_audio_player.dart | 102 ++++++++++++++++++ lib/features/navigator/app_router.dart | 6 ++ .../navigator/utils/navigation_commands.dart | 11 ++ lib/l10n/app_pl.arb | 4 + lib/utils/duration_utils.dart | 5 + linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 21 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 lib/features/digital_guide_view/tabs/micronavigation/data/models/micronavigation_response.dart create mode 100644 lib/features/digital_guide_view/tabs/micronavigation/data/repository/micronavigation_repository.dart create mode 100644 lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart create mode 100644 lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_expansion_tile_content.dart create mode 100644 lib/features/digital_guide_view/tabs/micronavigation/presentation/widgets/my_audio_player.dart create mode 100644 lib/utils/duration_utils.dart diff --git a/README.md b/README.md index beb7a6d7..595e4218 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ WIREDASH_SECRET="<...>" # can be left empty SKS_URL="https://<...>/api/v1" DIGITAL_GUIDE_URL="https://<...>/api" DIGITAL_GUIDE_AUTHORIZATION_TOKEN="<...>" +DIGITAL_GUIDE_ADDONS_URL="<...>" ``` If you need our server url please write us an email [kn.solvro@pwr.edu.pl](mailto:kn.solvro@pwr.edu.pl) or contact us via our [website](https://solvro.pwr.edu.pl/contact/) diff --git a/example.env b/example.env index 5afa411d..e4f15567 100644 --- a/example.env +++ b/example.env @@ -5,4 +5,5 @@ WIREDASH_ID="<...>" WIREDASH_SECRET="<...>" SKS_URL="<...>" DIGITAL_GUIDE_URL="<...>" -DIGITAL_GUIDE_AUTHORIZATION_TOKEN="<...>" \ No newline at end of file +DIGITAL_GUIDE_AUTHORIZATION_TOKEN="<...>" +DIGITAL_GUIDE_ADDONS_URL="<...>" \ No newline at end of file diff --git a/lib/config/env.dart b/lib/config/env.dart index b836a7bc..899c3f19 100644 --- a/lib/config/env.dart +++ b/lib/config/env.dart @@ -32,4 +32,6 @@ abstract class Env { @EnviedField() static final String digitalGuideAuthorizationToken = _Env.digitalGuideAuthorizationToken; + @EnviedField() + static final String digitalGuideAddonsUrl = _Env.digitalGuideAddonsUrl; } diff --git a/lib/config/ui_config.dart b/lib/config/ui_config.dart index 27caee8b..9778be5e 100644 --- a/lib/config/ui_config.dart +++ b/lib/config/ui_config.dart @@ -242,6 +242,8 @@ abstract class DigitalGuideConfig { EdgeInsets.symmetric(vertical: 24, horizontal: 24); static const borderRadiusSmall = 4.0; static const borderRadiusMedium = 8.0; + static const borderRadiusBig = 16.0; + static const borderRadiusHuge = 32.0; static const heightTiny = 4.0; static const heightSmall = 8.0; static const heightMedium = 16.0; @@ -252,6 +254,7 @@ abstract class DigitalGuideConfig { static const mediumButtonPadding = EdgeInsets.symmetric(vertical: 8, horizontal: 14); static const paddingMedium = 16.0; + static const paddingBig = 24.0; static const difficultiesCardIconSize = 35.0; static const photoRowHeight = 75.0; diff --git a/lib/features/digital_guide_view/data/models/digital_guide_response.dart b/lib/features/digital_guide_view/data/models/digital_guide_response.dart index b610bc52..cdbc2dcb 100644 --- a/lib/features/digital_guide_view/data/models/digital_guide_response.dart +++ b/lib/features/digital_guide_view/data/models/digital_guide_response.dart @@ -7,6 +7,7 @@ part "digital_guide_response.g.dart"; class DigitalGuideResponse with _$DigitalGuideResponse { const factory DigitalGuideResponse({ required int id, + @JsonKey(name: "external_id") required int externalId, required DigitalGuideTranslations translations, @JsonKey(name: "number_of_storeys") required int numberOfStoreys, @JsonKey( 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 7a92f0e9..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 @@ -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/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"; @@ -75,7 +76,11 @@ class DigitalGuideFeaturesSection extends ConsumerWidget { ), ( title: context.localize.micro_navigation, - content: [LocalizationExpansionTileContent()], + content: [ + MicronavigationExpansionTileContent( + digitalGuideData: digitalGuideData, + ), + ], ), ( title: context.localize.building_structure, diff --git a/lib/features/digital_guide_view/readme.md b/lib/features/digital_guide_view/readme.md index 26f1b80c..69458915 100644 --- a/lib/features/digital_guide_view/readme.md +++ b/lib/features/digital_guide_view/readme.md @@ -2,6 +2,7 @@ * Two variables should be added to .env * DIGITAL_GUIDE_URL * DIGITAL_GUIDE_AUTHORIZATION_TOKEN + * DIGITAL_GUIDE_URL (no token required) # Tips * All HTTP requests must include the authorization token ("Token ${Env.digitalGuideAuthorizationToken"}) @@ -13,8 +14,14 @@ * DIGITAL_GUIDE_URL/buildings/{id} * DIGITAL_GUIDE_URL/images/{id} 2) Surroundings data - * /surrounding/data/repository/surrounding_repository.dart + * tabs/surrounding/data/repository/surrounding_repository.dart * DIGITAL_GUIDE_URL/surroundings/{id} -3) Rooms data + +3) Micronavigation data + * /tabs/micronavigation/data/repository/micronavigation_repository.dart + * 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/micronavigation/data/models/micronavigation_response.dart b/lib/features/digital_guide_view/tabs/micronavigation/data/models/micronavigation_response.dart new file mode 100644 index 00000000..71e25ada --- /dev/null +++ b/lib/features/digital_guide_view/tabs/micronavigation/data/models/micronavigation_response.dart @@ -0,0 +1,50 @@ +// ignore_for_file: invalid_annotation_target + +import "package:freezed_annotation/freezed_annotation.dart"; + +part "micronavigation_response.freezed.dart"; +part "micronavigation_response.g.dart"; + +@freezed +class MicronavigationResponse with _$MicronavigationResponse { + @JsonSerializable(fieldRename: FieldRename.snake) + const factory MicronavigationResponse({ + required int id, + required int location, + required MicronavigationTranslations nameOverride, + required MicronavigationTranslations webContent, + required List languages, + }) = _MicronavigationResponse; + + factory MicronavigationResponse.fromJson(Map json) => + _$MicronavigationResponseFromJson(json); +} + +@freezed +class MicronavigationTranslations with _$MicronavigationTranslations { + const factory MicronavigationTranslations({ + String? pl, + String? en, + String? nb, + String? de, + String? uk, + }) = _MicronavigationTranslations; + + factory MicronavigationTranslations.fromJson(Map json) => + _$MicronavigationTranslationsFromJson(json); +} + +@freezed +class MicronavigationLanguage with _$MicronavigationLanguage { + @JsonSerializable(fieldRename: FieldRename.snake) + const factory MicronavigationLanguage({ + required int id, + required String langCode, + String? text, + String? sound, + required int order, + }) = _MicronavigationLanguage; + + factory MicronavigationLanguage.fromJson(Map json) => + _$MicronavigationLanguageFromJson(json); +} diff --git a/lib/features/digital_guide_view/tabs/micronavigation/data/repository/micronavigation_repository.dart b/lib/features/digital_guide_view/tabs/micronavigation/data/repository/micronavigation_repository.dart new file mode 100644 index 00000000..94461ab7 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/micronavigation/data/repository/micronavigation_repository.dart @@ -0,0 +1,37 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:riverpod_annotation/riverpod_annotation.dart"; + +import "../../../../../../api_base_rest/cache/cache.dart"; +import "../../../../../../config/env.dart"; +import "../../../../presentation/digital_guide_view.dart"; +import "../models/micronavigation_response.dart"; + +part "micronavigation_repository.g.dart"; + +const _ttlDays = 7; + +@riverpod +Future> getMicronavigationData( + Ref ref, + int id, +) async { + final micronavigationUrl = + "${Env.digitalGuideAddonsUrl}/beaconplus/?location=$id"; + + final responseData = await ref.getAndCacheData( + micronavigationUrl, + _ttlDays, + (List json) => json + .whereType>() + .map(MicronavigationResponse.fromJson) + .toIList(), + localizedOfflineMessage: DigitalGuideView.localizedOfflineMessage, + extraValidityCheck: (data) { + return data.isNotEmpty; + }, + onRetry: () => ref.invalidateSelf(), + ); + + return responseData; +} diff --git a/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart b/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart new file mode 100644 index 00000000..9c291905 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart @@ -0,0 +1,77 @@ +import "package:auto_route/auto_route.dart"; +import "package:flutter/material.dart"; + +import "../../../../../config/ui_config.dart"; +import "../../../../../theme/app_theme.dart"; +import "../../../../../utils/context_extensions.dart"; +import "../../../../../widgets/detail_views/detail_view_app_bar.dart"; +import "../../../../../widgets/my_html_widget.dart"; +import "../../../presentation/widgets/accessibility_button.dart"; +import "../data/models/micronavigation_response.dart"; +import "widgets/my_audio_player.dart"; + +@RoutePage() +class MicronavigationDetailView extends StatelessWidget { + const MicronavigationDetailView({ + super.key, + required this.micronavigationResponse, + }); + + final MicronavigationResponse micronavigationResponse; + + @override + Widget build(BuildContext context) { + final widgets = [ + const SizedBox(height: DigitalGuideConfig.heightMedium), + Text( + micronavigationResponse.nameOverride.pl ?? "", + style: context.textTheme.title.copyWith(fontSize: 24), + ), + const SizedBox(height: DigitalGuideConfig.heightBig), + Text( + context.localize.communique, + style: context.textTheme.title, + ), + const SizedBox(height: DigitalGuideConfig.heightMedium), + MyHtmlWidget(micronavigationResponse.webContent.pl ?? ""), + const SizedBox(height: DigitalGuideConfig.heightMedium), + Text( + context.localize.audio_message, + style: context.textTheme.title, + ), + const SizedBox(height: DigitalGuideConfig.heightMedium), + Text( + context.localize.audio_message_comment, + style: context.textTheme.body, + ), + Padding( + padding: DigitalGuideConfig.symetricalPaddingBig, + child: MyAudioPlayer( + audioUrl: micronavigationResponse.languages + .where((a) => a.langCode == "pl") + .firstOrNull + ?.sound ?? + "", + ), + ), + ]; + + return Scaffold( + backgroundColor: context.colorTheme.greyLight, + appBar: DetailViewAppBar( + actions: [AccessibilityButton()], + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: DigitalGuideConfig.paddingBig, + ), + 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/micronavigation/presentation/micronavigation_expansion_tile_content.dart b/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_expansion_tile_content.dart new file mode 100644 index 00000000..dc90acc6 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_expansion_tile_content.dart @@ -0,0 +1,75 @@ +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 "../../../../digital_guide_view/data/models/digital_guide_response.dart"; +import "../../../../navigator/utils/navigation_commands.dart"; +import "../../../presentation/widgets/digital_guide_nav_link.dart"; +import "../data/models/micronavigation_response.dart"; +import "../data/repository/micronavigation_repository.dart"; + +class MicronavigationExpansionTileContent extends ConsumerWidget { + const MicronavigationExpansionTileContent({ + super.key, + required this.digitalGuideData, + }); + + final DigitalGuideResponse digitalGuideData; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asyncMicronavigationData = ref.watch( + getMicronavigationDataProvider( + digitalGuideData.externalId, + ), + ); + + return asyncMicronavigationData.when( + data: (micronavigationDataList) => _MicronavigationExpansionTileContent( + micronavigationResponses: micronavigationDataList, + ), + error: (error, stackTrace) => MyErrorWidget(error), + loading: () => const Center(child: CircularProgressIndicator()), + ); + } +} + +class _MicronavigationExpansionTileContent extends ConsumerWidget { + const _MicronavigationExpansionTileContent({ + required this.micronavigationResponses, + }); + + final IList micronavigationResponses; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final widgets = micronavigationResponses.map((response) { + return Padding( + padding: const EdgeInsets.fromLTRB( + DigitalGuideConfig.paddingMedium, + DigitalGuideConfig.paddingMedium, + DigitalGuideConfig.paddingMedium, + 0, + ), + child: DigitalGuideNavLink( + onTap: () async { + await ref.navigateMicronavigationDetails(response); + }, + text: response.nameOverride.pl ?? "", + ), + ); + }).toIList(); + + return Padding( + padding: const EdgeInsets.only(bottom: 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/micronavigation/presentation/widgets/my_audio_player.dart b/lib/features/digital_guide_view/tabs/micronavigation/presentation/widgets/my_audio_player.dart new file mode 100644 index 00000000..a8bed12d --- /dev/null +++ b/lib/features/digital_guide_view/tabs/micronavigation/presentation/widgets/my_audio_player.dart @@ -0,0 +1,102 @@ +import "dart:async"; + +import "package:audioplayers/audioplayers.dart"; +import "package:flutter/material.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; + +import "../../../../../../config/ui_config.dart"; +import "../../../../../../theme/app_theme.dart"; +import "../../../../../../utils/duration_utils.dart"; + +class MyAudioPlayer extends HookWidget { + final String audioUrl; + + const MyAudioPlayer({super.key, required this.audioUrl}); + + @override + Widget build(BuildContext context) { + final audioPlayer = useMemoized(AudioPlayer.new); + final isPlaying = useState(false); + final currentTime = useState(Duration.zero); + final totalTime = useState(Duration.zero); + + useEffect( + () { + final stateSubscription = + audioPlayer.onPlayerStateChanged.listen((state) { + isPlaying.value = state == PlayerState.playing; + }); + + final durationSubscription = + audioPlayer.onDurationChanged.listen((duration) { + totalTime.value = duration; + }); + + final positionSubscription = + audioPlayer.onPositionChanged.listen((position) { + currentTime.value = position; + }); + + return () async { + unawaited(stateSubscription.cancel()); + unawaited(durationSubscription.cancel()); + unawaited(positionSubscription.cancel()); + unawaited(audioPlayer.dispose()); + }; + }, + [audioPlayer], + ); + + final togglePlayPause = useCallback( + () async { + if (isPlaying.value) { + await audioPlayer.pause(); + } else { + await audioPlayer.play(UrlSource(audioUrl)); + } + }, + [audioUrl], + ); + + final seekAudio = useCallback( + (double value) async { + final position = Duration(seconds: value.toInt()); + await audioPlayer.seek(position); + }, + [], + ); + + return Container( + padding: const EdgeInsets.all(DigitalGuideConfig.paddingSmall), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: + BorderRadius.circular(DigitalGuideConfig.borderRadiusHuge), + ), + child: Row( + children: [ + IconButton( + icon: Icon(isPlaying.value ? Icons.pause : Icons.play_arrow), + color: context.colorTheme.blackMirage, + onPressed: togglePlayPause, + ), + Text( + "${formatDurationToMinutesString(currentTime.value)} / ${formatDurationToMinutesString(totalTime.value)}", + style: + TextStyle(fontSize: 14, color: context.colorTheme.blackMirage), + ), + const SizedBox(width: DigitalGuideConfig.heightTiny), + Expanded( + child: Slider( + value: currentTime.value.inSeconds.toDouble(), + max: totalTime.value.inSeconds.toDouble(), + onChanged: seekAudio, + activeColor: context.colorTheme.blackMirage, + inactiveColor: Colors.grey[400], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/navigator/app_router.dart b/lib/features/navigator/app_router.dart index 7d59cbf1..ce4f28bc 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/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"; @@ -111,6 +113,10 @@ class AppRouter extends RootStackRouter { path: "/aboutUs", page: AboutUsRoute.page, ), + AutoRoute( + 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 7f3c2547..9f656428 100644 --- a/lib/features/navigator/utils/navigation_commands.dart +++ b/lib/features/navigator/utils/navigation_commands.dart @@ -6,6 +6,7 @@ import "package:logger/logger.dart"; import "../../../utils/launch_url_util.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"; @@ -89,6 +90,16 @@ extension NavigationX on WidgetRef { await _router.push(AdaptedToiletDetailRoute(adaptedToilet: adaptedToilet)); } + Future navigateMicronavigationDetails( + MicronavigationResponse micronavigationResponse, + ) async { + await _router.push( + MicronavigationDetailRoute( + micronavigationResponse: micronavigationResponse, + ), + ); + } + 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 63809452..2872a68a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -284,6 +284,10 @@ "strategic_club_abbr": "KS", "emptySection": "Brak członków zespołu w tej wersji", "digital_guide_offline" : "Cyfrowego Przewodnika", + "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", "platforms" : "Podesty", "stairs" : "Schody", "key_information" : "Najważniejsze informacje", diff --git a/lib/utils/duration_utils.dart b/lib/utils/duration_utils.dart new file mode 100644 index 00000000..82f87c39 --- /dev/null +++ b/lib/utils/duration_utils.dart @@ -0,0 +1,5 @@ +String formatDurationToMinutesString(Duration duration) { + final minutes = duration.inMinutes; + final seconds = duration.inSeconds % 60; + return "$minutes:${seconds.toString().padLeft(2, "0")}"; +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bfe..cc10c4daa2 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f16b4c34..8e2a1900 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux url_launcher_linux ) diff --git a/pubspec.yaml b/pubspec.yaml index 048e54c9..16dc6787 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -117,6 +117,7 @@ dependencies: separate: ^1.0.3 wiredash: ^2.2.2 flutter_hooks: ^0.20.5 + audioplayers: ^6.1.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 2cd4f252..12cc6b54 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); GeolocatorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 8ae151bc..8d18f963 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows firebase_core geolocator_windows permission_handler_windows From 10c98f9ebb7a66954ef2054a84222b8006e6cdf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Kowali=C5=84ski?= Date: Sun, 19 Jan 2025 18:29:12 +0100 Subject: [PATCH 2/5] feat(digital-guide): add accessibility modes dialog (#537) --- lib/config/ui_config.dart | 6 + .../widgets/accessibility_button.dart | 12 +- .../business/accessibility_mode_service.dart | 67 ++++++++ .../business/top_level_modes.dart | 8 + .../data/accessibility_mode_repository.dart | 23 +++ .../tabs/accessibility_dialog/data/modes.dart | 48 ++++++ .../presentation/accessibility_dialog.dart | 18 +++ .../presentation/checkboxes_list.dart | 35 ++++ .../presentation/labels.dart | 17 ++ .../presentation/mode_checkbox.dart | 40 +++++ .../presentation/red_dialog.dart | 151 ++++++++++++++++++ lib/l10n/app_pl.arb | 10 +- 12 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/business/accessibility_mode_service.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/business/top_level_modes.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/data/accessibility_mode_repository.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/data/modes.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/accessibility_dialog.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/labels.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/mode_checkbox.dart create mode 100644 lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/red_dialog.dart diff --git a/lib/config/ui_config.dart b/lib/config/ui_config.dart index 9778be5e..371e0f5f 100644 --- a/lib/config/ui_config.dart +++ b/lib/config/ui_config.dart @@ -171,6 +171,12 @@ abstract class FilterConfig { static const paddingMedium = 8.0; static const spacingBetweenWidgets = 12.0; static final radius = BorderRadius.circular(8); + static const buttonPadding = + EdgeInsets.symmetric(vertical: 10, horizontal: 20); +} + +class DialogsConfig { + static final padding = const EdgeInsets.all(20).copyWith(top: 6); } abstract class LottieAnimationConfig { diff --git a/lib/features/digital_guide_view/presentation/widgets/accessibility_button.dart b/lib/features/digital_guide_view/presentation/widgets/accessibility_button.dart index 1a9f1c11..d117c237 100644 --- a/lib/features/digital_guide_view/presentation/widgets/accessibility_button.dart +++ b/lib/features/digital_guide_view/presentation/widgets/accessibility_button.dart @@ -1,7 +1,10 @@ +import "dart:async"; + import "package:flutter/material.dart"; import "../../../../config/ui_config.dart"; import "../../../../theme/app_theme.dart"; +import "../../tabs/accessibility_dialog/presentation/accessibility_dialog.dart"; class AccessibilityButton extends StatelessWidget { @override @@ -9,7 +12,14 @@ class AccessibilityButton extends StatelessWidget { return Padding( padding: const EdgeInsets.only(right: 8), child: OutlinedButton( - onPressed: () {}, + onPressed: () { + unawaited( + showDialog( + context: context, + builder: (_) => const AccessibilityDialog(), + ), + ); + }, style: OutlinedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/business/accessibility_mode_service.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/business/accessibility_mode_service.dart new file mode 100644 index 00000000..6305bb78 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/business/accessibility_mode_service.dart @@ -0,0 +1,67 @@ +import "dart:async"; + +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:riverpod_annotation/riverpod_annotation.dart"; + +import "../data/accessibility_mode_repository.dart"; +import "../data/modes.dart"; + +part "accessibility_mode_service.g.dart"; + +// if the mode has children, it will be calculated based on them +// if the mode has a key, it will be calculated based on the repository +@riverpod +class AccessibilityModeService extends _$AccessibilityModeService { + @override + Future build(AccessibilityMode mode) async { + return switch (mode) { + ModeWithChildren() => _calculateModeWithChildrenState(mode), + ModeWithKey() => ref.watch( + accessibilityModeRepositoryProvider(mode).future, + ), + }; + } + + Future setMode({required bool newValue}) async { + final modeStronglyTyped = mode; // needed for typing system + await switch (modeStronglyTyped) { + ModeWithChildren() => + _setModeWithChildrenState(modeStronglyTyped, newValue), + ModeWithKey() => _setSingularModeState(modeStronglyTyped, newValue), + }; + } + + // true if any of its children are true + Future _calculateModeWithChildrenState(ModeWithChildren mode) async { + final submodesValues = await Future.wait( + mode.children.map( + (child) => ref.watch(accessibilityModeServiceProvider(child).future), + ), + ); + return submodesValues.anyIs(true); + } + + // sets all childrens' of the mode to newValue + Future _setModeWithChildrenState( + ModeWithChildren mode, + bool newValue, + ) async { + for (final child in mode.children) { + await ref + .read(accessibilityModeServiceProvider(child).notifier) + .setMode(newValue: newValue); + } + } + + // calls directly the repository + Future _setSingularModeState( + ModeWithKey modeStronglyTyped, + bool newValue, + ) { + return ref + .read( + accessibilityModeRepositoryProvider(modeStronglyTyped).notifier, + ) + .setMode(newValue: newValue); + } +} diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/business/top_level_modes.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/business/top_level_modes.dart new file mode 100644 index 00000000..604d59a3 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/business/top_level_modes.dart @@ -0,0 +1,8 @@ +import "../data/modes.dart"; + +final topLevelModes = [ + const MotorImpairment(), + const VisualImpairment(), + const SensorySensitivity(), + const CognitiveImpairment(), +]; diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/data/accessibility_mode_repository.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/data/accessibility_mode_repository.dart new file mode 100644 index 00000000..c89aeee2 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/data/accessibility_mode_repository.dart @@ -0,0 +1,23 @@ +import "dart:async"; + +import "package:riverpod_annotation/riverpod_annotation.dart"; + +import "../../../../../config/shared_prefs.dart"; +import "modes.dart"; + +part "accessibility_mode_repository.g.dart"; + +@riverpod +class AccessibilityModeRepository extends _$AccessibilityModeRepository { + @override + Future build(ModeWithKey mode) async { + final prefs = await ref.watch(sharedPreferencesSingletonProvider.future); + return prefs.getBool(mode.sharedPrefsKey) ?? false; + } + + Future setMode({required bool newValue}) async { + state = AsyncValue.data(newValue); + final prefs = await ref.watch(sharedPreferencesSingletonProvider.future); + await prefs.setBool(mode.sharedPrefsKey, newValue); + } +} diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/data/modes.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/data/modes.dart new file mode 100644 index 00000000..6b17f20c --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/data/modes.dart @@ -0,0 +1,48 @@ +// https://dart.dev/language/class-modifiers#sealed +sealed class AccessibilityMode { + const AccessibilityMode(); +} + +// this mode's state depends on the state of its children +sealed class ModeWithChildren extends AccessibilityMode { + const ModeWithChildren(this.children); + final List children; +} + +// this mode's state is stored locally in shared preferences +sealed class ModeWithKey extends AccessibilityMode { + const ModeWithKey(this.sharedPrefsKey); + final String sharedPrefsKey; +} + +class MotorImpairment extends ModeWithKey { + const MotorImpairment() : super("_prefs_accessibility_motor_impairment"); +} + +class Blind extends ModeWithKey { + const Blind() : super("_prefs_accessibility_blind"); +} + +class LowVision extends ModeWithKey { + const LowVision() : super("_prefs_accessibility_low_vision"); +} + +class SensorySensitivity extends ModeWithKey { + const SensorySensitivity() + : super("_prefs_accessibility_sensory_sensitivity"); +} + +class CognitiveImpairment extends ModeWithKey { + const CognitiveImpairment() + : super("_prefs_accessibility_cognitive_impairment"); +} + +class VisualImpairment extends ModeWithChildren { + const VisualImpairment() + : super( + const [ + LowVision(), + Blind(), + ], + ); +} diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/accessibility_dialog.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/accessibility_dialog.dart new file mode 100644 index 00000000..afa7cfaf --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/accessibility_dialog.dart @@ -0,0 +1,18 @@ +import "package:flutter/material.dart"; + +import "../../../../../utils/context_extensions.dart"; +import "checkboxes_list.dart"; +import "red_dialog.dart"; + +class AccessibilityDialog extends StatelessWidget { + const AccessibilityDialog({super.key}); + + @override + Widget build(BuildContext context) { + return RedDialog( + title: context.localize.accessibility_profiles, + subtitle: context.localize.you_can_adjust, + child: const CheckboxesList(), + ); + } +} diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart new file mode 100644 index 00000000..abd26349 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart @@ -0,0 +1,35 @@ +import "package:flutter/material.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; + +import "../business/top_level_modes.dart"; +import "../data/modes.dart"; +import "mode_checkbox.dart"; + +class _SubModePadding extends Padding { + const _SubModePadding({super.child}) + : super( + padding: const EdgeInsets.only(left: 25), + ); +} + +class CheckboxesList extends HookWidget { + const CheckboxesList({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + for (final mode in topLevelModes) + switch (mode) { + ModeWithChildren() => Column( + children: [ + for (final subMode in mode.children) + _SubModePadding(child: ModeCheckbox(subMode)), + ], + ), + ModeWithKey() => ModeCheckbox(mode), + }, + ], + ); + } +} diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/labels.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/labels.dart new file mode 100644 index 00000000..4c0a8869 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/labels.dart @@ -0,0 +1,17 @@ +import "package:flutter/material.dart"; + +import "../../../../../utils/context_extensions.dart"; +import "../data/modes.dart"; + +extension AccessibilityModeLocalizationX on AccessibilityMode { + String localizedLabel(BuildContext context) { + return switch (this) { + MotorImpairment() => context.localize.motorImpairment, + VisualImpairment() => context.localize.visualImpairment, + Blind() => context.localize.blind, + LowVision() => context.localize.lowVision, + SensorySensitivity() => context.localize.sensorySensitivity, + CognitiveImpairment() => context.localize.cognitiveImpairment, + }; + } +} diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/mode_checkbox.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/mode_checkbox.dart new file mode 100644 index 00000000..61d8b5b8 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/mode_checkbox.dart @@ -0,0 +1,40 @@ +import "dart:async"; + +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; + +import "../../../../../theme/app_theme.dart"; +import "../business/accessibility_mode_service.dart"; +import "../data/modes.dart"; +import "labels.dart"; + +class ModeCheckbox extends ConsumerWidget { + const ModeCheckbox(this.mode, {super.key}); + final AccessibilityMode mode; + @override + Widget build(BuildContext context, WidgetRef ref) { + final value = ref.watch(accessibilityModeServiceProvider(mode)); + + // ignore: avoid_positional_boolean_parameters + void onChanged(bool? value) { + if (value != null) { + unawaited( + ref + .read(accessibilityModeServiceProvider(mode).notifier) + .setMode(newValue: value), + ); + } + } + + return CheckboxListTile( + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + mode.localizedLabel(context), + style: context.aboutUsTheme.body, + ), + value: value.value ?? false, + onChanged: onChanged, + ); + } +} diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/red_dialog.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/red_dialog.dart new file mode 100644 index 00000000..2b9fa489 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/red_dialog.dart @@ -0,0 +1,151 @@ +import "package:flutter/material.dart"; + +import "../../../../../config/ui_config.dart"; +import "../../../../../theme/app_theme.dart"; +import "../../../../../utils/context_extensions.dart"; + +// pure UI, no logic, just a nice dialog with a title, subtitle and a child +class RedDialog extends StatelessWidget { + final String title; + final String subtitle; + final Widget child; + + const RedDialog({ + super.key, + required this.title, + required this.child, + required this.subtitle, + }); + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + child: DecoratedBox( + decoration: BoxDecoration( + color: context.colorTheme.whiteSoap, + borderRadius: BorderRadius.circular(8), + border: Border( + top: BorderSide( + color: context.colorTheme.orangePomegranade, + ), + bottom: BorderSide( + color: context.colorTheme.orangePomegranade, + width: 5, + ), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(64), + offset: const Offset(0, 4), + blurRadius: 4, + ), + ], + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 332), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _DialogHeader(title: title), + Flexible( + child: SingleChildScrollView( + child: _DialogContent(subtitle: subtitle, child: child), + ), + ), + ], + ), + ), + ), + ); + } +} + +class _DialogContent extends StatelessWidget { + const _DialogContent({ + required this.subtitle, + required this.child, + }); + + final String subtitle; + final Widget child; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + subtitle, + style: context.aboutUsTheme.body.copyWith( + height: 1.4, + color: context.colorTheme.greyPigeon, + ), + ), + ), + const SizedBox(height: 6), + child, + const _DialogFooter(), + ], + ); + } +} + +class _DialogFooter extends StatelessWidget { + const _DialogFooter(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: DialogsConfig.padding, + child: ElevatedButton( + onPressed: () { + // we're saving the changes in real time anyway + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: context.colorTheme.orangePomegranadeLighter, + elevation: 2, + padding: FilterConfig.buttonPadding, + shape: RoundedRectangleBorder(borderRadius: FilterConfig.radius), + ), + child: Center( + child: Text( + context.localize.apply, + style: context.textTheme.titleWhite, + ), + ), + ), + ); + } +} + +class _DialogHeader extends StatelessWidget { + const _DialogHeader({required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(20).copyWith(bottom: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + title, + style: context.textTheme.headline.copyWith(height: 1.4), + ), + ), + IconButton( + icon: const Icon(Icons.close), + color: context.colorTheme.greyPigeon, + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ); + } +} diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 2872a68a..671b6cf1 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -291,5 +291,13 @@ "platforms" : "Podesty", "stairs" : "Schody", "key_information" : "Najważniejsze informacje", - "working_hours" : "Godziny otwarcia" + "working_hours" : "Godziny otwarcia", + "motorImpairment": "Dysfunkcja ruchu", + "visualImpairment": "Dysfunkcja wzroku", + "blind": "Niewidomy", + "lowVision": "Słabo widzący", + "sensorySensitivity": "Wrażliwość sensoryczna", + "cognitiveImpairment": "Trudności poznawcze", + "accessibility_profiles":"Profile dostępności", + "you_can_adjust": "Możesz dostosować informacje pod swoje specjalne potrzeby" } From cc49d03a81a2579a51711c4dc2084491f537fdf8 Mon Sep 17 00:00:00 2001 From: Maja Mroczek <152724796+mmzek@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:02:53 +0100 Subject: [PATCH 3/5] fix: horizontals once and for all (let's hope) (#539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: even horizontal allignment * ffix: fix horizontals once and for all (let's hope) * fix: fix horizontal once and for all vol. 2 --------- Co-authored-by: Szymon Kowaliński --- lib/features/about_us_view/about_us_view.dart | 5 +- lib/features/app_changelog/app_changelog.dart | 5 +- .../widgets/app_changelog_list.dart | 2 +- .../sheet_layout_scheme.dart | 1 - .../department_detail_view.dart | 3 +- .../departments_view/departments_view.dart | 4 +- .../presentation/digital_guide_view.dart | 7 +- .../adapted_toilet_detail_view.dart | 3 +- .../micronavigation_detail_view.dart | 3 +- .../digital_guide_room_detail_view.dart | 3 +- lib/features/home_view/home_view.dart | 34 +++------ .../home_view/widgets/logo_app_bar.dart | 1 + lib/features/navigator/app_router.dart | 17 ++--- lib/features/navigator/root_view.dart | 16 ++--- .../science_club_detail_view.dart | 3 +- .../widgets/about_us_section_loading.dart | 4 +- .../science_clubs_filters/filters_sheet.dart | 61 ++++++++-------- .../widgets/sci_clubs_scaffold.dart | 17 ++++- .../presentation/sks_menu_screen.dart | 11 +-- .../presentation/sks_chart_sheet.dart | 70 ++++++++++--------- lib/main.dart | 4 +- .../detail_views/detail_view_app_bar.dart | 2 +- .../horizontal_symmetric_safe_area.dart | 46 ++++++++++++ lib/widgets/search_box_app_bar.dart | 2 +- 24 files changed, 188 insertions(+), 136 deletions(-) create mode 100644 lib/widgets/horizontal_symmetric_safe_area.dart diff --git a/lib/features/about_us_view/about_us_view.dart b/lib/features/about_us_view/about_us_view.dart index 65a895dd..0fd86b99 100644 --- a/lib/features/about_us_view/about_us_view.dart +++ b/lib/features/about_us_view/about_us_view.dart @@ -6,6 +6,7 @@ import "../../config/ui_config.dart"; import "../../utils/context_extensions.dart"; import "../../widgets/detail_views/detail_view_app_bar.dart"; import "../../widgets/detail_views/sliver_header_section.dart"; +import "../../widgets/horizontal_symmetric_safe_area.dart"; import "../../widgets/loading_widgets/scrolable_loader_builder.dart"; import "../../widgets/loading_widgets/simple_previews/preview_card_loading.dart"; import "../../widgets/my_error_widget.dart"; @@ -23,7 +24,7 @@ class AboutUsView extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar(), body: const _AboutUsView(), ); @@ -92,7 +93,7 @@ class _AboutUsLoading extends StatelessWidget { Padding( padding: const EdgeInsets.all(AboutUsConfig.defaultPadding), child: SizedBox( - height: MediaQuery.of(context).size.height / 4, + height: MediaQuery.sizeOf(context).height / 4, child: ScrollableLoaderBuilder( itemsSpacing: 8, scrollDirection: Axis.vertical, diff --git a/lib/features/app_changelog/app_changelog.dart b/lib/features/app_changelog/app_changelog.dart index 68aea9e8..b2e5d480 100644 --- a/lib/features/app_changelog/app_changelog.dart +++ b/lib/features/app_changelog/app_changelog.dart @@ -1,5 +1,6 @@ import "package:auto_route/auto_route.dart"; import "package:flutter/material.dart"; + import "../../theme/app_theme.dart"; import "repository/changelog_repository.dart"; import "widgets/app_changelog_header.dart"; @@ -19,8 +20,8 @@ class AppChangelog extends StatelessWidget { child: Container( padding: const EdgeInsets.all(25), constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.6, - maxWidth: MediaQuery.of(context).size.width * 0.7, + maxHeight: MediaQuery.sizeOf(context).height * 0.6, + maxWidth: MediaQuery.sizeOf(context).width * 0.7, ), decoration: BoxDecoration( color: context.colorTheme.whiteSoap, diff --git a/lib/features/app_changelog/widgets/app_changelog_list.dart b/lib/features/app_changelog/widgets/app_changelog_list.dart index 57572f5a..a3ef4212 100644 --- a/lib/features/app_changelog/widgets/app_changelog_list.dart +++ b/lib/features/app_changelog/widgets/app_changelog_list.dart @@ -40,7 +40,7 @@ class _ListItem extends StatelessWidget { return Row( children: [ Container( - width: MediaQuery.of(context).size.width * 0.2, + width: MediaQuery.sizeOf(context).width * 0.2, alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 8), decoration: BoxDecoration( diff --git a/lib/features/bottom_scroll_sheet/sheet_layout_scheme.dart b/lib/features/bottom_scroll_sheet/sheet_layout_scheme.dart index 8291d428..cf0a172e 100644 --- a/lib/features/bottom_scroll_sheet/sheet_layout_scheme.dart +++ b/lib/features/bottom_scroll_sheet/sheet_layout_scheme.dart @@ -21,7 +21,6 @@ class SheetLayoutScheme extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final appBar = SearchBoxAppBar( context, - primary: false, title: context.mapViewTexts().title, onQueryChanged: ref .watch(context.mapDataController().notifier) diff --git a/lib/features/department_detail_view/department_detail_view.dart b/lib/features/department_detail_view/department_detail_view.dart index 83bbe0ba..19e2d9c0 100644 --- a/lib/features/department_detail_view/department_detail_view.dart +++ b/lib/features/department_detail_view/department_detail_view.dart @@ -10,6 +10,7 @@ import "../../utils/determine_contact_icon.dart"; import "../../utils/where_non_null_iterable.dart"; import "../../widgets/detail_views/contact_section.dart"; import "../../widgets/detail_views/detail_view_app_bar.dart"; +import "../../widgets/horizontal_symmetric_safe_area.dart"; import "../../widgets/my_error_widget.dart"; import "repository/department_details_repository.dart"; import "utils/address_formatter.dart"; @@ -27,7 +28,7 @@ class DepartmentDetailView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(departmentDetailsRepositoryProvider(id)); - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar(), body: switch (state) { AsyncError(:final error) => MyErrorWidget(error), diff --git a/lib/features/departments_view/departments_view.dart b/lib/features/departments_view/departments_view.dart index ab7d7fce..858f93e2 100644 --- a/lib/features/departments_view/departments_view.dart +++ b/lib/features/departments_view/departments_view.dart @@ -5,6 +5,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "../../config/ui_config.dart"; import "../../utils/context_extensions.dart"; +import "../../widgets/horizontal_symmetric_safe_area.dart"; import "../../widgets/my_error_widget.dart"; import "../../widgets/search_box_app_bar.dart"; import "../../widgets/search_not_found.dart"; @@ -34,8 +35,9 @@ class _DepartmentsView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: SearchBoxAppBar( + primary: true, addLeadingPopButton: true, context, title: context.localize.departments, diff --git a/lib/features/digital_guide_view/presentation/digital_guide_view.dart b/lib/features/digital_guide_view/presentation/digital_guide_view.dart index ce201c33..df08a0c9 100644 --- a/lib/features/digital_guide_view/presentation/digital_guide_view.dart +++ b/lib/features/digital_guide_view/presentation/digital_guide_view.dart @@ -10,6 +10,7 @@ import "../../../utils/context_extensions.dart"; import "../../../utils/determine_contact_icon.dart"; import "../../../widgets/detail_views/contact_section.dart"; import "../../../widgets/detail_views/detail_view_app_bar.dart"; +import "../../../widgets/horizontal_symmetric_safe_area.dart"; import "../../../widgets/my_error_widget.dart"; import "../../../widgets/zoomable_images.dart"; import "../data/models/digital_guide_response.dart"; @@ -40,12 +41,12 @@ class DigitalGuideView extends ConsumerWidget { ref.watch(digitalGuideRepositoryProvider(ourId)); return asyncDigitalGuideData.when( data: (data) => _DigitalGuideView(data.digitalGuideData, data.photoUrl), - error: (error, stackTrace) => Scaffold( + error: (error, stackTrace) => HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar(), body: MyErrorWidget(error), ), // TODO(Bartosh): shimmer loading - loading: () => Scaffold( + loading: () => HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar(), body: const Center( child: CircularProgressIndicator(), @@ -103,7 +104,7 @@ class _DigitalGuideView extends ConsumerWidget { const SizedBox(height: DigitalGuideConfig.heightHuge), ]; - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar( actions: [AccessibilityButton()], ), diff --git a/lib/features/digital_guide_view/tabs/adapted_toilets/presentation/adapted_toilet_detail_view.dart b/lib/features/digital_guide_view/tabs/adapted_toilets/presentation/adapted_toilet_detail_view.dart index 12ad86fc..3b116bd1 100644 --- a/lib/features/digital_guide_view/tabs/adapted_toilets/presentation/adapted_toilet_detail_view.dart +++ b/lib/features/digital_guide_view/tabs/adapted_toilets/presentation/adapted_toilet_detail_view.dart @@ -7,6 +7,7 @@ import "../../../../../config/ui_config.dart"; import "../../../../../theme/app_theme.dart"; import "../../../../../utils/context_extensions.dart"; import "../../../../../widgets/detail_views/detail_view_app_bar.dart"; +import "../../../../../widgets/horizontal_symmetric_safe_area.dart"; import "../../../presentation/widgets/accessibility_button.dart"; import "../../../presentation/widgets/bullet_list.dart"; import "../../../presentation/widgets/digital_guide_image.dart"; @@ -86,7 +87,7 @@ class AdaptedToiletDetailView extends ConsumerWidget { ), ]; - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar( actions: [AccessibilityButton()], ), diff --git a/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart b/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart index 9c291905..ac8a1b7a 100644 --- a/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart +++ b/lib/features/digital_guide_view/tabs/micronavigation/presentation/micronavigation_detail_view.dart @@ -5,6 +5,7 @@ import "../../../../../config/ui_config.dart"; import "../../../../../theme/app_theme.dart"; import "../../../../../utils/context_extensions.dart"; import "../../../../../widgets/detail_views/detail_view_app_bar.dart"; +import "../../../../../widgets/horizontal_symmetric_safe_area.dart"; import "../../../../../widgets/my_html_widget.dart"; import "../../../presentation/widgets/accessibility_button.dart"; import "../data/models/micronavigation_response.dart"; @@ -56,7 +57,7 @@ class MicronavigationDetailView extends StatelessWidget { ), ]; - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( backgroundColor: context.colorTheme.greyLight, appBar: DetailViewAppBar( actions: [AccessibilityButton()], 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 index 8f64328f..73e38483 100644 --- 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 @@ -8,6 +8,7 @@ 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 "../../../../../widgets/horizontal_symmetric_safe_area.dart"; import "../../../presentation/widgets/accessibility_button.dart"; import "../../../presentation/widgets/bullet_list.dart"; import "../../../presentation/widgets/digital_guide_nav_link.dart"; @@ -91,7 +92,7 @@ class DigitalGuideRoomDetailView extends ConsumerWidget { text: context.localize.stairs, ), ]; - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar( actions: [AccessibilityButton()], ), diff --git a/lib/features/home_view/home_view.dart b/lib/features/home_view/home_view.dart index d0f1eef7..9f276d79 100644 --- a/lib/features/home_view/home_view.dart +++ b/lib/features/home_view/home_view.dart @@ -17,9 +17,6 @@ class HomeView extends StatelessWidget { @override Widget build(BuildContext context) { - final safeAreaInsets = MediaQuery.of(context).padding; - final horizontalPadding = safeAreaInsets.left; - final sections = [ const AcademicCalendarConsumer(), const Padding( @@ -31,31 +28,16 @@ class HomeView extends StatelessWidget { ].lock; return Scaffold( + primary: false, backgroundColor: context.colorTheme.whiteSoap, - appBar: PreferredSize( - preferredSize: const Size.fromHeight(kToolbarHeight), - child: SafeArea( - child: Padding( - padding: EdgeInsets.only(left: horizontalPadding), - child: LogoAppBar(context), - ), - ), - ), - body: SafeArea( - child: Padding( - padding: EdgeInsets.only( - left: horizontalPadding, // Align with the top bar - right: safeAreaInsets.right, - ), - child: KeepAliveHomeViewProviders( - child: ListView.separated( - itemBuilder: (context, index) => sections[index], - separatorBuilder: (context, index) => SizedBox( - height: index == 1 ? 0 : HomeViewConfig.paddingMedium, - ), - itemCount: sections.length, - ), + appBar: LogoAppBar(context), + body: KeepAliveHomeViewProviders( + child: ListView.separated( + itemBuilder: (context, index) => sections[index], + separatorBuilder: (context, index) => SizedBox( + height: index == 1 ? 0 : HomeViewConfig.paddingMedium, ), + itemCount: sections.length, ), ), ); diff --git a/lib/features/home_view/widgets/logo_app_bar.dart b/lib/features/home_view/widgets/logo_app_bar.dart index 50dc5a03..ed9df0a3 100644 --- a/lib/features/home_view/widgets/logo_app_bar.dart +++ b/lib/features/home_view/widgets/logo_app_bar.dart @@ -13,6 +13,7 @@ class LogoAppBar extends AppBar { double toolbarHeight = kToolbarHeight, super.actions, }) : super( + primary: false, title: AppBarLogo(logoSize: logoSize), centerTitle: false, titleSpacing: 0, diff --git a/lib/features/navigator/app_router.dart b/lib/features/navigator/app_router.dart index ce4f28bc..bca064ed 100644 --- a/lib/features/navigator/app_router.dart +++ b/lib/features/navigator/app_router.dart @@ -25,7 +25,6 @@ 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 { @@ -103,22 +102,24 @@ class AppRouter extends RootStackRouter { page: ScienceClubDetailRoute.page, ), AutoRoute( - path: "/digital-guide/:id", - page: DigitalGuideRoute.page, + path: "/aboutUs", + page: AboutUsRoute.page, ), AutoRoute( - page: AdaptedToiletDetailRoute.page, + path: "/digital-guide/:id", + page: DigitalGuideRoute.page, ), AutoRoute( - path: "/aboutUs", - page: AboutUsRoute.page, + path: "/digital-guide/:id/room-details", + page: DigitalGuideRoomDetailRoute.page, ), AutoRoute( - path: "/digital-guide/:id/micronavigationDetails", + path: "/digital-guide/:id/micronavigation-details", page: MicronavigationDetailRoute.page, ), AutoRoute( - page: DigitalGuideRoomDetailRoute.page, + path: "/digital-guide/:id/adapted-toilet-details", + page: AdaptedToiletDetailRoute.page, ), ]; } diff --git a/lib/features/navigator/root_view.dart b/lib/features/navigator/root_view.dart index dc2dc577..dc4305c4 100644 --- a/lib/features/navigator/root_view.dart +++ b/lib/features/navigator/root_view.dart @@ -2,6 +2,7 @@ import "package:auto_route/auto_route.dart"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "../../widgets/horizontal_symmetric_safe_area.dart"; import "../app_changelog/update_changelog_wrapper.dart"; import "../bottom_nav_bar/bottom_nav_bar.dart"; import "navigation_controller.dart"; @@ -20,17 +21,12 @@ class RootView extends ConsumerWidget { onPopWithResult: specialPop ? (result) async => ref.handleAndroidSpecialPop() : null, child: UpdateChangelogWrapper( - child: Scaffold( - body: Padding( - padding: EdgeInsets.symmetric( - horizontal: MediaQuery.viewPaddingOf(context).horizontal, - ), - child: AutoRouter( - // this widget act as nested [Navigator] for the app - key: ref.watch(navigatorKeyProvider), - ), - ), + child: HorizontalSymmetricSafeAreaScaffold( bottomNavigationBar: const BottomNavBar(), + body: AutoRouter( + // this widget acts as nested [Navigator] for the app + key: ref.watch(navigatorKeyProvider), + ), ), ), ), diff --git a/lib/features/science_club_detail_view/science_club_detail_view.dart b/lib/features/science_club_detail_view/science_club_detail_view.dart index ba477912..232492ec 100644 --- a/lib/features/science_club_detail_view/science_club_detail_view.dart +++ b/lib/features/science_club_detail_view/science_club_detail_view.dart @@ -11,6 +11,7 @@ import "../../utils/where_non_null_iterable.dart"; import "../../widgets/detail_views/contact_section.dart"; import "../../widgets/detail_views/detail_view_app_bar.dart"; import "../../widgets/detail_views/sliver_header_section.dart"; +import "../../widgets/horizontal_symmetric_safe_area.dart"; import "../../widgets/loading_widgets/contact_section_loading.dart"; import "../../widgets/loading_widgets/header_section_loading.dart"; import "../../widgets/loading_widgets/shimmer_loading.dart"; @@ -32,7 +33,7 @@ class ScienceClubDetailView extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar(), body: _SciClubDetailDataView(id), ); diff --git a/lib/features/science_club_detail_view/widgets/about_us_section_loading.dart b/lib/features/science_club_detail_view/widgets/about_us_section_loading.dart index 67a42bf2..3290ec84 100644 --- a/lib/features/science_club_detail_view/widgets/about_us_section_loading.dart +++ b/lib/features/science_club_detail_view/widgets/about_us_section_loading.dart @@ -26,7 +26,7 @@ class AboutUsSectionLoading extends StatelessWidget { ), const SizedBox(height: DetailViewsConfig.spacerHeight), SizedBox( - height: MediaQuery.of(context).size.height / 10, + height: MediaQuery.sizeOf(context).height / 10, child: ShimmerLoadingItem( child: ScrollableLoaderBuilder( mainAxisItemSize: 16, @@ -35,7 +35,7 @@ class AboutUsSectionLoading extends StatelessWidget { itemBuilder: (BuildContext context, int index) { return Container( height: 16, - width: MediaQuery.of(context).size.width, + width: MediaQuery.sizeOf(context).width, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), diff --git a/lib/features/science_clubs_filters/filters_sheet.dart b/lib/features/science_clubs_filters/filters_sheet.dart index f880a8ed..add56a3f 100644 --- a/lib/features/science_clubs_filters/filters_sheet.dart +++ b/lib/features/science_clubs_filters/filters_sheet.dart @@ -6,6 +6,7 @@ import "../../config/ui_config.dart"; import "../../hooks/use_filters_sheet_height.dart"; import "../../theme/app_theme.dart"; import "../../utils/context_extensions.dart"; +import "../../widgets/horizontal_symmetric_safe_area.dart"; import "filters_controller.dart"; import "filters_search_controller.dart"; import "widgets/apply_filters_button.dart"; @@ -25,37 +26,39 @@ class FiltersSheet extends StatelessWidget { searchFiltersControllerProvider, areFiltersEnabledProvider, ], - child: SizedBox( - height: sheetHeight, - child: Padding( - padding: const EdgeInsets.only(top: 16), - child: Column( - children: [ - FiltersHeader(), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ListView( - shrinkWrap: true, - children: [ - const TypesWrap(), - const DepartmentsWrap(), - const TagsWrap(), - const _NoFiltersFound(), - const SizedBox( - height: FilterConfig.spacingBetweenWidgets, - ), - ApplyFiltersButton( - onPressed: context.maybePop, - ), - const SizedBox( - height: FilterConfig.spacingBetweenWidgets, - ), - ], + child: HorizontalSymmetricSafeArea( + child: SizedBox( + height: sheetHeight, + child: Padding( + padding: const EdgeInsets.only(top: 16), + child: Column( + children: [ + FiltersHeader(), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ListView( + shrinkWrap: true, + children: [ + const TypesWrap(), + const DepartmentsWrap(), + const TagsWrap(), + const _NoFiltersFound(), + const SizedBox( + height: FilterConfig.spacingBetweenWidgets, + ), + ApplyFiltersButton( + onPressed: context.maybePop, + ), + const SizedBox( + height: FilterConfig.spacingBetweenWidgets, + ), + ], + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart b/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart index 05497b8b..ad04102c 100644 --- a/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart +++ b/lib/features/science_clubs_view/widgets/sci_clubs_scaffold.dart @@ -2,6 +2,7 @@ import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "../../../utils/context_extensions.dart"; +import "../../../widgets/horizontal_symmetric_safe_area.dart"; import "../../../widgets/search_box_app_bar.dart"; import "../../science_clubs_filters/widgets/filters_fab.dart"; import "../controllers/science_clubs_view_controller.dart"; @@ -12,17 +13,27 @@ class SciClubsScaffold extends ConsumerWidget { final bool showFab; @override Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: SearchBoxAppBar( addLeadingPopButton: true, + primary: true, context, title: context.localize.study_circles, onQueryChanged: ref .watch(searchScienceClubsControllerProvider.notifier) .onTextChanged, ), - floatingActionButton: showFab ? const FiltersFAB() : null, - body: child, + body: Stack( + children: [ + if (child != null) child!, + if (showFab) + Positioned( + right: 16, // the left view padding is applied globally + bottom: MediaQuery.viewPaddingOf(context).bottom + 16, + child: const FiltersFAB(), + ), + ], + ), ); } } diff --git a/lib/features/sks-menu/presentation/sks_menu_screen.dart b/lib/features/sks-menu/presentation/sks_menu_screen.dart index 9a6eac66..5058267f 100644 --- a/lib/features/sks-menu/presentation/sks_menu_screen.dart +++ b/lib/features/sks-menu/presentation/sks_menu_screen.dart @@ -12,6 +12,7 @@ import "../../../config/ui_config.dart"; import "../../../gen/assets.gen.dart"; import "../../../utils/context_extensions.dart"; import "../../../widgets/detail_views/detail_view_app_bar.dart"; +import "../../../widgets/horizontal_symmetric_safe_area.dart"; import "../../../widgets/my_error_widget.dart"; import "../../../widgets/my_text_button.dart"; import "../../../widgets/text_and_url_widget.dart"; @@ -52,7 +53,7 @@ class SksMenuView extends HookConsumerWidget { isLastMenuButtonClicked: isLastMenuButtonClicked.value, ); }, - error: (error, stackTrace) => Scaffold( + error: (error, stackTrace) => HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar( actions: const [ SksUserDataButton(), @@ -60,8 +61,8 @@ class SksMenuView extends HookConsumerWidget { ), body: MyErrorWidget(error), ), - loading: () => const Scaffold( - body: Center( + loading: () => HorizontalSymmetricSafeAreaScaffold( + body: const Center( child: SksMenuViewLoading(), ), ), @@ -83,7 +84,7 @@ class _SksMenuView extends ConsumerWidget { if (!isLastMenuButtonClicked && !sksMenuData.isMenuOnline) { return const _SKSMenuUnavailableAnimation(); } - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( appBar: DetailViewAppBar( actions: const [ SksUserDataButton(), @@ -141,7 +142,7 @@ class _SKSMenuUnavailableAnimation extends HookWidget { ) * 0.6; - return Scaffold( + return HorizontalSymmetricSafeAreaScaffold( backgroundColor: context.colorTheme.whiteSoap, appBar: DetailViewAppBar( actions: const [ diff --git a/lib/features/sks_chart/presentation/sks_chart_sheet.dart b/lib/features/sks_chart/presentation/sks_chart_sheet.dart index 81358471..86f97f37 100644 --- a/lib/features/sks_chart/presentation/sks_chart_sheet.dart +++ b/lib/features/sks_chart/presentation/sks_chart_sheet.dart @@ -6,6 +6,7 @@ import "../../../config/ui_config.dart"; import "../../../hooks/use_filters_sheet_height.dart"; import "../../../theme/app_theme.dart"; import "../../../utils/context_extensions.dart"; +import "../../../widgets/horizontal_symmetric_safe_area.dart"; import "../../../widgets/my_error_widget.dart"; import "../../../widgets/text_and_url_widget.dart"; import "../../bottom_scroll_sheet/drag_handle.dart"; @@ -36,42 +37,45 @@ class SksChartSheet extends ConsumerWidget { return switch (asyncChartData) { AsyncError(:final error) => MyErrorWidget(error), AsyncLoading() => const SizedBox.shrink(), - AsyncValue() => SizedBox( - height: sheetHeight, - width: double.infinity, - child: Column( - children: [ - Padding( - padding: SksChartConfig.paddingLargeLTR - .copyWith(bottom: SksChartConfig.paddingMedium), - child: const _SksSheetHeader(), - ), - Expanded( - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: SksChartConfig.paddingMedium, - ), - child: SksChartCard( - currentNumberOfUsers: currentNumberOfUsers, - maxNumberOfUsers: maxNumberOfUsers, - chartData: asyncChartData.value ?? const IList.empty(), - ), - ), - Padding( - padding: const EdgeInsets.all( - SksChartConfig.paddingSmall, + AsyncValue() => HorizontalSymmetricSafeArea( + child: SizedBox( + height: sheetHeight, + width: double.infinity, + child: Column( + children: [ + Padding( + padding: SksChartConfig.paddingLargeLTR + .copyWith(bottom: SksChartConfig.paddingMedium), + child: const _SksSheetHeader(), + ), + Expanded( + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: SksChartConfig.paddingMedium, + ), + child: SksChartCard( + currentNumberOfUsers: currentNumberOfUsers, + maxNumberOfUsers: maxNumberOfUsers, + chartData: + asyncChartData.value ?? const IList.empty(), + ), ), - child: TextAndUrl( - SksChartConfig.sksChartDataUrl, - "${context.localize.data_come_from_website}: ", + Padding( + padding: const EdgeInsets.all( + SksChartConfig.paddingSmall, + ), + child: TextAndUrl( + SksChartConfig.sksChartDataUrl, + "${context.localize.data_come_from_website}: ", + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), }; diff --git a/lib/main.dart b/lib/main.dart index 30cba1ca..7dd15eec 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,9 +40,7 @@ class MyApp extends ConsumerWidget { theme: context.wiredashTheme, child: MaterialApp.router( builder: (context, child) => InAppReviewWidget( - child: UpdateDialogWrapper( - child: child!, - ), + child: UpdateDialogWrapper(child: child!), ), title: MyAppConfig.title, localizationsDelegates: AppLocalizations.localizationsDelegates, diff --git a/lib/widgets/detail_views/detail_view_app_bar.dart b/lib/widgets/detail_views/detail_view_app_bar.dart index dc49e5d1..22c9efda 100644 --- a/lib/widgets/detail_views/detail_view_app_bar.dart +++ b/lib/widgets/detail_views/detail_view_app_bar.dart @@ -7,10 +7,10 @@ class DetailViewAppBar extends AppBar { super.key, super.actions, }) : super( + title: const DetailViewPopButton(), centerTitle: false, automaticallyImplyLeading: false, scrolledUnderElevation: 0, titleSpacing: 4, - title: const DetailViewPopButton(), ); } diff --git a/lib/widgets/horizontal_symmetric_safe_area.dart b/lib/widgets/horizontal_symmetric_safe_area.dart new file mode 100644 index 00000000..ce1997ac --- /dev/null +++ b/lib/widgets/horizontal_symmetric_safe_area.dart @@ -0,0 +1,46 @@ +import "dart:math"; + +import "package:flutter/material.dart"; + +extension MediaQueryPaddingExtensionX on BuildContext { + EdgeInsets get maxOfHorizontalViewPaddings { + final padding = MediaQuery.paddingOf(this); + final maxPadding = max(padding.left, padding.right); + return EdgeInsets.only( + right: maxPadding, + left: maxPadding, + ); + } +} + +class HorizontalSymmetricSafeArea extends StatelessWidget { + const HorizontalSymmetricSafeArea({ + super.key, + required this.child, + }); + final Widget child; + + @override + Widget build(BuildContext context) { + return Padding( + padding: context.maxOfHorizontalViewPaddings, + child: SafeArea( + left: false, + right: false, + child: child, + ), + ); + } +} + +class HorizontalSymmetricSafeAreaScaffold extends Scaffold { + HorizontalSymmetricSafeAreaScaffold({ + super.key, + required Widget body, + super.bottomNavigationBar, + super.appBar, + super.backgroundColor, + }) : super( + body: HorizontalSymmetricSafeArea(child: body), + ); +} diff --git a/lib/widgets/search_box_app_bar.dart b/lib/widgets/search_box_app_bar.dart index 3a63951d..b4d45e51 100644 --- a/lib/widgets/search_box_app_bar.dart +++ b/lib/widgets/search_box_app_bar.dart @@ -14,11 +14,11 @@ class SearchBoxAppBar extends AppBar { required String title, required void Function(String query) onQueryChanged, super.actions, - super.primary, super.key, VoidCallback? onSearchBoxTap, double bottomPadding = defaultBottomPadding, bool addLeadingPopButton = false, + super.primary = false, }) : super( title: Text(title), titleTextStyle: context.textTheme.headline, From a44f76e5b51cad9597e5a83019632f702e191211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Kowali=C5=84ski?= Date: Mon, 20 Jan 2025 20:41:13 +0100 Subject: [PATCH 4/5] fix(digital-guide): missing checkbox --- .../tabs/accessibility_dialog/presentation/checkboxes_list.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart index abd26349..77da5ed9 100644 --- a/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart +++ b/lib/features/digital_guide_view/tabs/accessibility_dialog/presentation/checkboxes_list.dart @@ -23,6 +23,7 @@ class CheckboxesList extends HookWidget { switch (mode) { ModeWithChildren() => Column( children: [ + ModeCheckbox(mode), for (final subMode in mode.children) _SubModePadding(child: ModeCheckbox(subMode)), ], From 3abd0010542387d8e12bc0d8b87c9768c37b2964 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, 23 Jan 2025 15:32:08 +0100 Subject: [PATCH 5/5] feat(digital-guide): add lodge section (#545) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(digital-guide): setup data layer * feat(digital-guide): setup presentation layer * feat(digital-guide): docs * apply pr suggestion Co-authored-by: Szymon Kowaliński * feta(digital-guide): apply pr suggestion --------- Co-authored-by: Szymon Kowaliński --- .../digital_guide_features_section.dart | 9 ++ lib/features/digital_guide_view/readme.md | 3 + .../data/models/digital_guide_lodge.dart | 38 ++++++++ .../data/repository/lodges_repository.dart | 25 ++++++ ...al_guide_lodge_expansion_tile_content.dart | 89 +++++++++++++++++++ lib/l10n/app_pl.arb | 6 +- 6 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 lib/features/digital_guide_view/tabs/lodge/data/models/digital_guide_lodge.dart create mode 100644 lib/features/digital_guide_view/tabs/lodge/data/repository/lodges_repository.dart create mode 100644 lib/features/digital_guide_view/tabs/lodge/presentation/digital_guide_lodge_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 ce6af862..0633b0e9 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/lodge/presentation/digital_guide_lodge_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"; @@ -102,6 +103,14 @@ class DigitalGuideFeaturesSection extends ConsumerWidget { ), ], ), + ( + title: context.localize.lodge, + content: [ + DigitalGuideLodgeExpansionTileContent( + digitalGuideData, + ), + ], + ), ]; return SliverList( diff --git a/lib/features/digital_guide_view/readme.md b/lib/features/digital_guide_view/readme.md index 69458915..bad0fde8 100644 --- a/lib/features/digital_guide_view/readme.md +++ b/lib/features/digital_guide_view/readme.md @@ -25,3 +25,6 @@ * /rooms/data/repository/rooms_repository.dart * DIGITAL_GUIDE_URL/rooms/{id} +5) Lodges data + * /lodges/?building={buildingId} + diff --git a/lib/features/digital_guide_view/tabs/lodge/data/models/digital_guide_lodge.dart b/lib/features/digital_guide_view/tabs/lodge/data/models/digital_guide_lodge.dart new file mode 100644 index 00000000..7f69fa54 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/lodge/data/models/digital_guide_lodge.dart @@ -0,0 +1,38 @@ +import "package:freezed_annotation/freezed_annotation.dart"; + +part "digital_guide_lodge.freezed.dart"; +part "digital_guide_lodge.g.dart"; + +@freezed +class DigitalGuideLodge with _$DigitalGuideLodge { + const factory DigitalGuideLodge({ + required int id, + required DigitalGuideTranslationsLodge translations, + @JsonKey(name: "images") required List? imagesIds, + }) = _DigitalGuideLodge; + + factory DigitalGuideLodge.fromJson(Map json) => + _$DigitalGuideLodgeFromJson(json); +} + +@freezed +class DigitalGuideTranslationsLodge with _$DigitalGuideTranslationsLodge { + const factory DigitalGuideTranslationsLodge({ + required DigitalGuideTranslationLodge pl, + }) = _DigitalGuideTranslationsLodge; + + factory DigitalGuideTranslationsLodge.fromJson(Map json) => + _$DigitalGuideTranslationsLodgeFromJson(json); +} + +@freezed +class DigitalGuideTranslationLodge with _$DigitalGuideTranslationLodge { + @JsonSerializable(fieldRename: FieldRename.snake) + const factory DigitalGuideTranslationLodge({ + required String location, + required String workingDaysAndHours, + required String comment, + }) = _DigitalGuideTranslationLodge; + factory DigitalGuideTranslationLodge.fromJson(Map json) => + _$DigitalGuideTranslationLodgeFromJson(json); +} diff --git a/lib/features/digital_guide_view/tabs/lodge/data/repository/lodges_repository.dart b/lib/features/digital_guide_view/tabs/lodge/data/repository/lodges_repository.dart new file mode 100644 index 00000000..748b1220 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/lodge/data/repository/lodges_repository.dart @@ -0,0 +1,25 @@ +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_lodge.dart"; + +part "lodges_repository.g.dart"; + +@riverpod +Future> lodgesRepository( + Ref ref, + DigitalGuideResponse building, +) async { + final endpoint = "lodges/?building=${building.id}"; + return ref.getAndCacheDataFromDigitalGuide( + endpoint, + (List json) => json + .whereType>() + .map(DigitalGuideLodge.fromJson) + .toIList(), + onRetry: () => ref.invalidateSelf(), + ); +} diff --git a/lib/features/digital_guide_view/tabs/lodge/presentation/digital_guide_lodge_expansion_tile_content.dart b/lib/features/digital_guide_view/tabs/lodge/presentation/digital_guide_lodge_expansion_tile_content.dart new file mode 100644 index 00000000..6e255f71 --- /dev/null +++ b/lib/features/digital_guide_view/tabs/lodge/presentation/digital_guide_lodge_expansion_tile_content.dart @@ -0,0 +1,89 @@ +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 "../../../../../widgets/my_error_widget.dart"; +import "../../../data/models/digital_guide_response.dart"; +import "../../../presentation/widgets/digital_guide_photo_row.dart"; +import "../data/models/digital_guide_lodge.dart"; +import "../data/repository/lodges_repository.dart"; + +class DigitalGuideLodgeExpansionTileContent extends ConsumerWidget { + const DigitalGuideLodgeExpansionTileContent(this.digitalGuideResponse); + + final DigitalGuideResponse digitalGuideResponse; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final lodgeResponse = + ref.watch(lodgesRepositoryProvider(digitalGuideResponse)); + return lodgeResponse.when( + data: (data) => + _DigitalGuideLodgeExpansionTileContent(lodge: data.firstOrNull), + error: (error, _) => MyErrorWidget(error), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ); + } +} + +class _DigitalGuideLodgeExpansionTileContent extends StatelessWidget { + final DigitalGuideLodge? lodge; + + const _DigitalGuideLodgeExpansionTileContent({ + required this.lodge, + }); + + @override + Widget build(BuildContext context) { + if (lodge == null) { + return Center(child: Text(context.localize.no_lodge_in_the_building)); + } + final lodgeInformation = lodge!.translations.pl; + return Padding( + padding: const EdgeInsets.all(DigitalGuideConfig.paddingMedium), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(context.localize.localization, style: context.textTheme.title), + Padding( + padding: const EdgeInsets.symmetric( + vertical: DigitalGuideConfig.heightSmall, + ), + child: Text(lodgeInformation.location), + ), + Text(context.localize.working_hours, style: context.textTheme.title), + const SizedBox( + height: DigitalGuideConfig.heightSmall, + ), + Text(lodgeInformation.workingDaysAndHours), + const SizedBox( + height: DigitalGuideConfig.heightSmall, + ), + if (lodgeInformation.comment.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + bottom: DigitalGuideConfig.paddingSmall, + ), + child: Text( + context.localize.additional_information, + style: context.textTheme.title, + ), + ), + Text(lodgeInformation.comment), + if (lodgeInformation.comment.isNotEmpty) + const SizedBox( + height: DigitalGuideConfig.heightMedium, + ), + DigitalGuidePhotoRow( + imagesIDs: lodge!.imagesIds?.toIList() ?? const IList.empty(), + ), + ], + ), + ); + } +} diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 671b6cf1..127b4550 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -287,7 +287,6 @@ "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", "platforms" : "Podesty", "stairs" : "Schody", "key_information" : "Najważniejsze informacje", @@ -299,5 +298,8 @@ "sensorySensitivity": "Wrażliwość sensoryczna", "cognitiveImpairment": "Trudności poznawcze", "accessibility_profiles":"Profile dostępności", - "you_can_adjust": "Możesz dostosować informacje pod swoje specjalne potrzeby" + "you_can_adjust": "Możesz dostosować informacje pod swoje specjalne potrzeby", + "additional_information": "Dodatkowe informacje", + "lodge": "Portiernia", + "no_lodge_in_the_building": "W tym budynku nie ma portierni" }