From 146950453fb52636a1fe1e90e8886f8ddd61e27b Mon Sep 17 00:00:00 2001 From: Piotr Bonar Date: Fri, 14 Feb 2025 22:47:46 +0100 Subject: [PATCH] feat(digital-guide): create loading screen #483 (#571) * chore: show digital guide * feat: add loading view * feat: made the shimmer loading visible * chore: fixed automatically * chore: fixed automatically v2 * chore: reusing variables defined in config in loading view * chore: automatical format update * chore: next try to eliminate the errors * chore: next try to eliminate the errors v2 * chore: next try to eliminate the errors v3 * chore: add fvm artifacts and resolve linter --- .fvm/flutter_sdk | 2 +- .fvm/fvm_config.json | 2 +- .fvmrc | 3 +- .../buildings_view/building_tile.dart | 40 ++--- .../presentation/digital_guide_view.dart | 30 ++-- .../widgets/digital_guide_loading_view.dart | 142 ++++++++++++++++++ 6 files changed, 183 insertions(+), 36 deletions(-) create mode 100644 lib/features/digital_guide_view/presentation/widgets/digital_guide_loading_view.dart diff --git a/.fvm/flutter_sdk b/.fvm/flutter_sdk index e048e3a5..eaa506b3 120000 --- a/.fvm/flutter_sdk +++ b/.fvm/flutter_sdk @@ -1 +1 @@ -/Users/shark/fvm/versions/stable \ No newline at end of file +/Users/shark/fvm/versions/3.27.4 \ No newline at end of file diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 1c922f80..04655715 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,3 +1,3 @@ { - "flutterSdkVersion": "stable" + "flutterSdkVersion": "3.27.4" } \ No newline at end of file diff --git a/.fvmrc b/.fvmrc index a6427fd2..c2783c69 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,4 +1,3 @@ { - "flutter": "stable", - "flavors": {} + "flutter": "3.27.4" } \ No newline at end of file diff --git a/lib/features/buildings_view/building_tile.dart b/lib/features/buildings_view/building_tile.dart index c410d6cc..ef515c43 100644 --- a/lib/features/buildings_view/building_tile.dart +++ b/lib/features/buildings_view/building_tile.dart @@ -3,9 +3,11 @@ import "dart:async"; 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/wide_tile_card.dart"; +import "../navigator/utils/navigation_commands.dart"; import "controllers.dart"; import "model/building_model.dart"; import "utils/utils.dart"; @@ -36,25 +38,25 @@ class BuildingTile extends ConsumerWidget { ); }, ), - // if (building.externalDigitalGuideMode != null && - // building.externalDigitalGuideIdOrURL != null) - // Positioned( - // top: 2, - // right: WideTileCardConfig.imageSize, - // child: IconButton( - // iconSize: 18, - // visualDensity: VisualDensity.compact, - // icon: Icon( - // Icons.info, - // color: isActive - // ? context.colorTheme.whiteSoap - // : context.colorTheme.greyPigeon, - // ), - // onPressed: () { - // unawaited(ref.navigateBuildingDetailAction(building)); - // }, - // ), - // ), + if (building.externalDigitalGuideMode != null && + building.externalDigitalGuideIdOrURL != null) + Positioned( + top: 2, + right: WideTileCardConfig.imageSize, + child: IconButton( + iconSize: 18, + visualDensity: VisualDensity.compact, + icon: Icon( + Icons.info, + color: isActive + ? context.colorTheme.whiteSoap + : context.colorTheme.greyPigeon, + ), + onPressed: () { + unawaited(ref.navigateBuildingDetailAction(building)); + }, + ), + ), ], ); } 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 5e70e509..9bc29320 100644 --- a/lib/features/digital_guide_view/presentation/digital_guide_view.dart +++ b/lib/features/digital_guide_view/presentation/digital_guide_view.dart @@ -19,6 +19,7 @@ import "../data/repository/digital_guide_repository.dart"; import "widgets/accessibility_button.dart"; import "widgets/digital_guide_data_source_link.dart"; import "widgets/digital_guide_features_section.dart"; +import "widgets/digital_guide_loading_view.dart"; import "widgets/headlines_section.dart"; import "widgets/report_change_button.dart"; @@ -43,19 +44,22 @@ class DigitalGuideView extends ConsumerWidget { final asyncDigitalGuideData = ref.watch(digitalGuideRepositoryProvider(ourId)); return asyncDigitalGuideData.when( - data: (data) => - _DigitalGuideView(data.digitalGuideData, data.photoUrl, building), - error: (error, stackTrace) => HorizontalSymmetricSafeAreaScaffold( - appBar: DetailViewAppBar(), - body: MyErrorWidget(error), - ), - // TODO(Bartosh): shimmer loading - loading: () => HorizontalSymmetricSafeAreaScaffold( - appBar: DetailViewAppBar(), - body: const Center( - child: CircularProgressIndicator(), - ), - ), + data: (data) { + return _DigitalGuideView( + data.digitalGuideData, + data.photoUrl, + building, + ); + }, + error: (error, stackTrace) { + return HorizontalSymmetricSafeAreaScaffold( + appBar: DetailViewAppBar(), + body: MyErrorWidget(error), + ); + }, + loading: () { + return const DigitalGuideLoadingView(); + }, ); } } diff --git a/lib/features/digital_guide_view/presentation/widgets/digital_guide_loading_view.dart b/lib/features/digital_guide_view/presentation/widgets/digital_guide_loading_view.dart new file mode 100644 index 00000000..b11983f0 --- /dev/null +++ b/lib/features/digital_guide_view/presentation/widgets/digital_guide_loading_view.dart @@ -0,0 +1,142 @@ +import "package:flutter/material.dart"; +import "../../../../config/ui_config.dart" + show DetailViewsConfig, DigitalGuideConfig, GuideDetailViewConfig; +import "../../../../widgets/loading_widgets/shimmer_loading.dart"; + +class DigitalGuideLoadingView extends StatelessWidget { + const DigitalGuideLoadingView({super.key}); + + @override + Widget build(BuildContext context) { + return const Shimmer( + linearGradient: shimmerGradient, + child: Column( + children: [ + _DigitalGuideHeaderLoading(), + _DigitalGuideTitleSectionLoading(), + _DigitalGuideInfoSectionLoading(), + Expanded( + child: _DigitalGuideTilesLoading(), + ), + ], + ), + ); + } +} + +class _DigitalGuideTitleSectionLoading extends StatelessWidget { + const _DigitalGuideTitleSectionLoading(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: DigitalGuideConfig.borderRadiusBig), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShimmerLoadingItem( + child: Container( + color: Colors.white, + width: 100, + height: DigitalGuideConfig.paddingBig, + ), + ), + const SizedBox(height: DigitalGuideConfig.paddingSmall), + ShimmerLoadingItem( + child: Container( + color: Colors.white, + width: MediaQuery.of(context).size.width - + 2 * DigitalGuideConfig.paddingBig, + height: DigitalGuideConfig.paddingBig, + ), + ), + ], + ), + ); + } +} + +class _DigitalGuideHeaderLoading extends StatelessWidget { + const _DigitalGuideHeaderLoading(); + + @override + Widget build(BuildContext context) { + return ShimmerLoadingItem( + child: Container( + margin: const EdgeInsets.only(top: DigitalGuideConfig.borderRadiusBig), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(GuideDetailViewConfig.borderRadius), + ), + width: double.infinity, + height: DetailViewsConfig.imageHeight, + ), + ); + } +} + +class _DigitalGuideInfoSectionLoading extends StatelessWidget { + const _DigitalGuideInfoSectionLoading(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: DigitalGuideConfig.borderRadiusBig), + child: ShimmerLoadingItem( + child: Container( + color: Colors.white, + width: double.infinity, + height: 180, + ), + ), + ); + } +} + +class _DigitalGuideTilesLoading extends StatelessWidget { + const _DigitalGuideTilesLoading(); + + static const int itemCount = 5; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: DigitalGuideConfig.borderRadiusBig, + ), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: itemCount, + itemBuilder: (context, index) { + return const Padding( + padding: EdgeInsets.symmetric( + vertical: GuideDetailViewConfig.paddingMedium, + ), + child: _LoadingTile(), + ); + }, + ), + ); + } +} + +class _LoadingTile extends StatelessWidget { + const _LoadingTile(); + + @override + Widget build(BuildContext context) { + return ShimmerLoadingItem( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(GuideDetailViewConfig.borderRadius), + ), + width: double.infinity, + height: DigitalGuideConfig.photoRowHeight, + ), + ); + } +}