diff --git a/.gitignore b/.gitignore index a4fb7a9d..e4612c80 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ pubspec.lock **/android/gradlew.bat **/android/local.properties **/android/**/GeneratedPluginRegistrant.java +**/android/app/.cxx # iOS/XCode related **/ios/**/*.mode1v3 diff --git a/example/lib/src/screens/main/layouts/vertical.dart b/example/lib/src/screens/main/layouts/vertical.dart index d933dcd3..517d64f8 100644 --- a/example/lib/src/screens/main/layouts/vertical.dart +++ b/example/lib/src/screens/main/layouts/vertical.dart @@ -25,39 +25,37 @@ class _VerticalLayout extends StatelessWidget { controller: _bottomSheetOuterController, ), floatingActionButton: selectedTab == 1 && - context - .watch() - .constructedRegions - .isNotEmpty + context.select( + (provider) => + provider.constructedRegions.isNotEmpty && + !provider.isDownloadSetupPanelVisible, + ) ? DelayedControllerAttachmentBuilder( listenable: _bottomSheetOuterController, - builder: (context, _) => AnimatedBuilder( - animation: _bottomSheetOuterController, - builder: (context, _) { - final pixels = _bottomSheetOuterController.isAttached - ? _bottomSheetOuterController.pixels - : 0; - return FloatingActionButton( - onPressed: () async { - await _bottomSheetOuterController.animateTo( - 2 / 3, - duration: const Duration(milliseconds: 250), - curve: Curves.easeOut, - ); - if (!context.mounted) return; - prepareDownloadConfigView( - context, - shouldShowConfig: pixels > 33, - ); - }, - tooltip: - pixels <= 33 ? 'Show regions' : 'Configure download', - child: pixels <= 33 - ? const Icon(Icons.library_add_check) - : const Icon(Icons.tune), - ); - }, - ), + builder: (context, _) { + final pixels = _bottomSheetOuterController.isAttached + ? _bottomSheetOuterController.pixels + : 0; + return FloatingActionButton( + onPressed: () async { + await _bottomSheetOuterController.animateTo( + 2 / 3, + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + ); + if (!context.mounted) return; + prepareDownloadConfigView( + context, + shouldShowConfig: pixels > 33, + ); + }, + tooltip: + pixels <= 33 ? 'Show regions' : 'Configure download', + child: pixels <= 33 + ? const Icon(Icons.library_add_check) + : const Icon(Icons.tune), + ); + }, ) : null, bottomNavigationBar: NavigationBar( diff --git a/example/lib/src/screens/main/map_view/components/additional_overlay/additional_overlay.dart b/example/lib/src/screens/main/map_view/components/additional_overlay/additional_overlay.dart index 213fb39d..5def22c1 100644 --- a/example/lib/src/screens/main/map_view/components/additional_overlay/additional_overlay.dart +++ b/example/lib/src/screens/main/map_view/components/additional_overlay/additional_overlay.dart @@ -48,14 +48,10 @@ class AdditionalOverlay extends StatelessWidget { listenable: bottomSheetOuterController, builder: (context, child) { if (!bottomSheetOuterController.isAttached) return child!; - return AnimatedBuilder( - animation: bottomSheetOuterController, - builder: (context, child) => _HeightZero( - useChildHeight: showShapeSelector && - bottomSheetOuterController.pixels <= 33, - child: child!, - ), - child: child, + return _HeightZero( + useChildHeight: showShapeSelector && + bottomSheetOuterController.pixels <= 33, + child: child!, ); }, child: Container( diff --git a/example/lib/src/screens/main/secondary_view/contents/download_configuration/components/config_options/config_options.dart b/example/lib/src/screens/main/secondary_view/contents/download_configuration/components/config_options/config_options.dart index 8c9ec803..93c07e36 100644 --- a/example/lib/src/screens/main/secondary_view/contents/download_configuration/components/config_options/config_options.dart +++ b/example/lib/src/screens/main/secondary_view/contents/download_configuration/components/config_options/config_options.dart @@ -43,122 +43,119 @@ class _ConfigOptionsState extends State { final fromRecovery = context .select((p) => p.fromRecovery); - return SingleChildScrollView( - child: Column( - children: [ - StoreSelector( - storeName: storeName, - onStoreNameSelected: (storeName) => context - .read() - .selectedStoreName = storeName, - enabled: fromRecovery == null, - ), - const Divider(height: 24), - Row( - children: [ - const Tooltip(message: 'Zoom Levels', child: Icon(Icons.search)), - const SizedBox(width: 8), - Expanded( - child: RangeSlider( - values: RangeValues(minZoom.toDouble(), maxZoom.toDouble()), - max: 20, - divisions: 20, - onChanged: fromRecovery != null - ? null - : (r) => context.read() - ..minZoom = r.start.toInt() - ..maxZoom = r.end.toInt(), - ), + return Column( + children: [ + StoreSelector( + storeName: storeName, + onStoreNameSelected: (storeName) => context + .read() + .selectedStoreName = storeName, + enabled: fromRecovery == null, + ), + const Divider(height: 24), + Row( + children: [ + const Tooltip(message: 'Zoom Levels', child: Icon(Icons.search)), + const SizedBox(width: 8), + Expanded( + child: RangeSlider( + values: RangeValues(minZoom.toDouble(), maxZoom.toDouble()), + max: 20, + divisions: 20, + onChanged: fromRecovery != null + ? null + : (r) => context.read() + ..minZoom = r.start.toInt() + ..maxZoom = r.end.toInt(), ), - Text( - '${minZoom.toString().padLeft(2, '0')} - ' - '${maxZoom.toString().padLeft(2, '0')}', + ), + Text( + '${minZoom.toString().padLeft(2, '0')} - ' + '${maxZoom.toString().padLeft(2, '0')}', + ), + ], + ), + const Divider(height: 24), + _SliderOption( + icon: const Icon(Icons.call_split), + tooltipMessage: 'Parallel Threads', + descriptor: 'threads', + value: parallelThreads, + min: 1, + max: 10, + onChanged: (v) => + context.read().parallelThreads = v, + ), + const SizedBox(height: 8), + _SliderOption( + icon: const Icon(Icons.speed), + tooltipMessage: 'Rate Limit', + descriptor: 'tps max', + value: rateLimit, + min: 1, + max: 200, + onChanged: (v) => + context.read().rateLimit = v, + ), + const SizedBox(height: 8), + Row( + children: [ + const Tooltip( + message: 'Max Buffer Length', + child: Icon(Icons.memory), + ), + const SizedBox(width: 6), + Expanded( + child: Slider( + value: maxBufferLength.toDouble(), + max: 1000, + divisions: 1000, + onChanged: (r) => context + .read() + .maxBufferLength = r.toInt(), ), - ], - ), - const Divider(height: 24), - _SliderOption( - icon: const Icon(Icons.call_split), - tooltipMessage: 'Parallel Threads', - descriptor: 'threads', - value: parallelThreads, - min: 1, - max: 10, - onChanged: (v) => context - .read() - .parallelThreads = v, - ), - const SizedBox(height: 8), - _SliderOption( - icon: const Icon(Icons.speed), - tooltipMessage: 'Rate Limit', - descriptor: 'tps max', - value: rateLimit, - min: 1, - max: 200, - onChanged: (v) => - context.read().rateLimit = v, - ), - const SizedBox(height: 8), - Row( - children: [ - const Tooltip( - message: 'Max Buffer Length', - child: Icon(Icons.memory), + ), + SizedBox( + width: 71, + child: Text( + maxBufferLength == 0 ? 'Disabled' : '$maxBufferLength tiles', + textAlign: TextAlign.end, ), - const SizedBox(width: 6), - Expanded( - child: Slider( - value: maxBufferLength.toDouble(), - max: 1000, - divisions: 1000, - onChanged: (r) => context - .read() - .maxBufferLength = r.toInt(), - ), - ), - SizedBox( - width: 71, - child: Text( - maxBufferLength == 0 ? 'Disabled' : '$maxBufferLength tiles', - textAlign: TextAlign.end, - ), - ), - ], - ), - const Divider(height: 24), - _ToggleOption( - icon: const Icon(Icons.file_copy), - title: 'Skip Existing Tiles', - description: "Don't attempt tiles that are already cached", - value: skipExistingTiles, - onChanged: (v) => context - .read() - .skipExistingTiles = v, - ), - const SizedBox(height: 8), - _ToggleOption( - icon: const Icon(Icons.waves), - title: 'Skip Sea Tiles', - description: - "Don't cache tiles with sea/ocean fill as the only visible " - 'element', - value: skipSeaTiles, - onChanged: (v) => - context.read().skipSeaTiles = v, - ), - const SizedBox(height: 8), - _ToggleOption( - icon: const Icon(Icons.plus_one), - title: 'Retry Failed Tiles', - description: 'Retries tiles that failed their HTTP request once', - value: retryFailedRequestTiles, - onChanged: (v) => context - .read() - .retryFailedRequestTiles = v, - ), - ], - ), + ), + ], + ), + const Divider(height: 24), + _ToggleOption( + icon: const Icon(Icons.file_copy), + title: 'Skip Existing Tiles', + description: "Don't attempt tiles that are already cached", + value: skipExistingTiles, + onChanged: (v) => context + .read() + .skipExistingTiles = v, + ), + const SizedBox(height: 8), + _ToggleOption( + icon: const Icon(Icons.waves), + title: 'Skip Sea Tiles', + description: + "Don't cache tiles with sea/ocean fill as the only visible " + 'element', + value: skipSeaTiles, + onChanged: (v) => + context.read().skipSeaTiles = v, + ), + const SizedBox(height: 8), + _ToggleOption( + icon: const Icon(Icons.plus_one), + title: 'Retry Failed Tiles', + description: 'Retries tiles that failed their HTTP request once', + value: retryFailedRequestTiles, + onChanged: (v) => context + .read() + .retryFailedRequestTiles = v, + ), + ], ); } } diff --git a/example/lib/src/screens/main/secondary_view/contents/download_configuration/download_configuration_view_bottom_sheet.dart b/example/lib/src/screens/main/secondary_view/contents/download_configuration/download_configuration_view_bottom_sheet.dart index 7f202016..e8b53127 100644 --- a/example/lib/src/screens/main/secondary_view/contents/download_configuration/download_configuration_view_bottom_sheet.dart +++ b/example/lib/src/screens/main/secondary_view/contents/download_configuration/download_configuration_view_bottom_sheet.dart @@ -1,7 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../../../../shared/state/download_configuration_provider.dart'; +import '../../../../../shared/state/region_selection_provider.dart'; +import '../../../../../shared/state/selected_tab_state.dart'; import '../../layouts/bottom_sheet/components/scrollable_provider.dart'; import '../../layouts/bottom_sheet/utils/tab_header.dart'; +import 'components/config_options/config_options.dart'; +import 'components/confirmation_panel/confirmation_panel.dart'; class DownloadConfigurationViewBottomSheet extends StatelessWidget { const DownloadConfigurationViewBottomSheet({super.key}); @@ -10,9 +16,55 @@ class DownloadConfigurationViewBottomSheet extends StatelessWidget { Widget build(BuildContext context) => CustomScrollView( controller: BottomSheetScrollableProvider.innerScrollControllerOf(context), - slivers: const [ - TabHeader(title: 'Download Configuration'), - SliverToBoxAdapter(child: SizedBox(height: 6)), + slivers: [ + const TabHeader(title: 'Download Configuration'), + SliverToBoxAdapter( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(99), + ), + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(4), + alignment: Alignment.centerLeft, + child: TextButton.icon( + onPressed: () { + final regionSelectionProvider = + context.read(); + final downloadConfigProvider = + context.read(); + + regionSelectionProvider.isDownloadSetupPanelVisible = false; + + if (downloadConfigProvider.fromRecovery == null) return; + + regionSelectionProvider.clearConstructedRegions(); + downloadConfigProvider.fromRecovery = null; + + selectedTabState.value = 2; + }, + icon: const Icon(Icons.arrow_back), + label: const Text('Return to selection'), + ), + ), + ), + const SliverToBoxAdapter(child: Divider()), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(8) + + const EdgeInsets.symmetric(horizontal: 16), + child: const ConfigOptions(), + ), + ), + const SliverToBoxAdapter(child: Divider()), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(8) + + const EdgeInsets.symmetric(horizontal: 16), + child: const ConfirmationPanel(), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 8)), ], ); } diff --git a/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/statistics.dart b/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/statistics.dart index 08036662..49b7d25b 100644 --- a/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/statistics.dart +++ b/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/statistics.dart @@ -7,21 +7,29 @@ import 'components/timing/timing.dart'; import 'components/title_bar/title_bar.dart'; class DownloadStatistics extends StatelessWidget { - const DownloadStatistics({super.key}); + const DownloadStatistics({ + super.key, + required this.showTitle, + }); + + final bool showTitle; @override - Widget build(BuildContext context) => const Column( + Widget build(BuildContext context) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TitleBar(), - SizedBox(height: 24), - TimingStats(), - SizedBox(height: 24), - ProgressIndicatorBars(), - SizedBox(height: 16), - ProgressIndicatorText(), - SizedBox(height: 24), - TileDisplay(), + if (showTitle) ...[ + const TitleBar(), + const SizedBox(height: 24), + ] else + const SizedBox(height: 6), + const TimingStats(), + const SizedBox(height: 24), + const ProgressIndicatorBars(), + const SizedBox(height: 16), + const ProgressIndicatorText(), + const SizedBox(height: 24), + const TileDisplay(), ], ); } diff --git a/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_bottom_sheet.dart b/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_bottom_sheet.dart index 8b137891..97d8fadf 100644 --- a/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_bottom_sheet.dart +++ b/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_bottom_sheet.dart @@ -1 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../../../../shared/state/download_provider.dart'; +import '../../../../../shared/state/region_selection_provider.dart'; +import '../../layouts/bottom_sheet/components/scrollable_provider.dart'; +import '../../layouts/bottom_sheet/utils/tab_header.dart'; +import 'components/confirm_cancellation_dialog.dart'; +import 'components/statistics/statistics.dart'; +class DownloadingViewBottomSheet extends StatefulWidget { + const DownloadingViewBottomSheet({ + super.key, + }); + + @override + State createState() => + _DownloadingViewBottomSheetState(); +} + +class _DownloadingViewBottomSheetState + extends State { + bool _isPausing = false; + + @override + Widget build(BuildContext context) => CustomScrollView( + controller: + BottomSheetScrollableProvider.innerScrollControllerOf(context), + slivers: [ + if (context.select((p) => p.isComplete)) + const TabHeader(title: 'Download Complete') + else if (context.select((p) => p.isPaused)) + const TabHeader(title: 'Download Paused') + else + const TabHeader(title: 'Downloading Map'), + SliverToBoxAdapter( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(99), + ), + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(4) + + const EdgeInsets.symmetric(horizontal: 8), + child: context + .select((p) => p.isComplete) + ? Align( + alignment: Alignment.centerRight, + child: FilledButton.icon( + onPressed: () { + context.read() + ..isDownloadSetupPanelVisible = false + ..clearConstructedRegions() + ..clearCoordinates(); + context.read().reset(); + }, + label: const Text('Reset'), + icon: const Icon(Icons.done_all), + ), + ) + : IntrinsicHeight( + child: Row( + children: [ + TextButton.icon( + onPressed: () async { + if (context + .read() + .isComplete) { + context.read() + ..isDownloadSetupPanelVisible = false + ..clearConstructedRegions() + ..clearCoordinates(); + context.read().reset(); + return; + } + + await showDialog( + context: context, + builder: (context) => + const ConfirmCancellationDialog(), + ); + }, + icon: const Icon(Icons.cancel), + label: const Text('Cancel'), + ), + const Spacer(), + if (context.select( + (p) => !p.isComplete, + )) + _isPausing + ? const AspectRatio( + aspectRatio: 1, + child: Center( + child: SizedBox.square( + dimension: 24, + child: CircularProgressIndicator + .adaptive(), + ), + ), + ) + : context.select( + (p) => p.isPaused, + ) + ? TextButton.icon( + onPressed: () { + context + .read() + .resume(); + setState(() {}); + }, + icon: const Icon(Icons.play_arrow), + label: const Text('Resume'), + ) + : TextButton.icon( + onPressed: () async { + setState(() => _isPausing = true); + await context + .read() + .pause(); + setState(() => _isPausing = false); + }, + icon: const Icon(Icons.pause), + label: const Text('Pause'), + ), + ], + ), + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(8) + + const EdgeInsets.symmetric(horizontal: 16), + child: const DownloadStatistics(showTitle: false), + ), + ), + ], + ); +} diff --git a/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_side.dart b/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_side.dart index 6495438d..4e6b0c63 100644 --- a/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_side.dart +++ b/example/lib/src/screens/main/secondary_view/contents/downloading/downloading_view_side.dart @@ -101,7 +101,7 @@ class _DownloadingViewSideState extends State { child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(16), - child: DownloadStatistics(), + child: DownloadStatistics(showTitle: true), ), ), ), diff --git a/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/example_app_limitations_text.dart b/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/example_app_limitations_text.dart index 46832528..304e6fca 100644 --- a/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/example_app_limitations_text.dart +++ b/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/example_app_limitations_text.dart @@ -1,5 +1,6 @@ const exampleAppLimitationsText = 'There are some limitations to the example app which do not exist in FMTC, ' 'because it is difficult to express in this UI design.\nEach store only ' - 'contains tiles from a single URL template. Only a single tile layer is ' - 'used/available (only a single URL template can be used at any one time).'; + 'contains tiles from a single URL template. URL transformers are not ' + 'supported. Only a single tile layer is used/available (only a single URL ' + 'template can be used at any one time).'; diff --git a/example/lib/src/screens/main/secondary_view/contents/region_selection/region_selection_view_bottom_sheet.dart b/example/lib/src/screens/main/secondary_view/contents/region_selection/region_selection_view_bottom_sheet.dart index 91c7dc07..fbce3ecd 100644 --- a/example/lib/src/screens/main/secondary_view/contents/region_selection/region_selection_view_bottom_sheet.dart +++ b/example/lib/src/screens/main/secondary_view/contents/region_selection/region_selection_view_bottom_sheet.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import '../../../../../shared/state/region_selection_provider.dart'; import '../../layouts/bottom_sheet/components/scrollable_provider.dart'; import '../../layouts/bottom_sheet/utils/tab_header.dart'; +import 'components/shared/to_config_method.dart'; import 'components/sub_regions_list/components/no_sub_regions.dart'; import 'components/sub_regions_list/sub_regions_list.dart'; @@ -21,16 +22,47 @@ class RegionSelectionViewBottomSheet extends StatelessWidget { BottomSheetScrollableProvider.innerScrollControllerOf(context), slivers: [ const TabHeader(title: 'Download Selection'), + if (hasConstructedRegions) + const SubRegionsList() + else + const NoSubRegions(), const SliverToBoxAdapter(child: SizedBox(height: 6)), - SliverPadding( - padding: hasConstructedRegions - ? const EdgeInsets.only(bottom: 16 + 52) - : EdgeInsets.zero, - sliver: hasConstructedRegions - ? const SubRegionsList() - : const NoSubRegions(), + SliverFillRemaining( + hasScrollBody: false, + child: Align( + alignment: Alignment.bottomRight, + child: Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(99), + ), + child: IntrinsicHeight( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () => context + .read() + .clearConstructedRegions(), + icon: const Icon(Icons.delete_forever), + ), + const SizedBox(width: 8), + SizedBox( + height: double.infinity, + child: FilledButton.icon( + onPressed: () => prepareDownloadConfigView(context), + label: const Text('Configure Download'), + icon: const Icon(Icons.tune), + ), + ), + ], + ), + ), + ), + ), ), - const SliverToBoxAdapter(child: SizedBox(height: 6)), ], ); } diff --git a/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/bottom_sheet.dart b/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/bottom_sheet.dart index e4e36927..6da745d4 100644 --- a/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/bottom_sheet.dart +++ b/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/bottom_sheet.dart @@ -2,8 +2,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../../../../../shared/state/download_provider.dart'; import '../../../../../shared/state/region_selection_provider.dart'; import '../../contents/download_configuration/download_configuration_view_bottom_sheet.dart'; +import '../../contents/downloading/downloading_view_bottom_sheet.dart'; import '../../contents/home/home_view_bottom_sheet.dart'; import '../../contents/recovery/recovery_view_bottom_sheet.dart'; import '../../contents/region_selection/region_selection_view_bottom_sheet.dart'; @@ -95,11 +97,15 @@ class _SecondaryViewBottomSheetState extends State { width: double.infinity, child: switch (widget.selectedTab) { 0 => const HomeViewBottomSheet(), - 1 => context.select( - (p) => p.isDownloadSetupPanelVisible, + 1 => context.select( + (p) => p.isFocused, ) - ? const DownloadConfigurationViewBottomSheet() - : const RegionSelectionViewBottomSheet(), + ? const DownloadingViewBottomSheet() + : context.select( + (p) => p.isDownloadSetupPanelVisible, + ) + ? const DownloadConfigurationViewBottomSheet() + : const RegionSelectionViewBottomSheet(), 2 => const RecoveryViewBottomSheet(), _ => Placeholder(key: ValueKey(widget.selectedTab)), }, diff --git a/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/utils/tab_header.dart b/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/utils/tab_header.dart index 078f2621..950a6fd7 100644 --- a/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/utils/tab_header.dart +++ b/example/lib/src/screens/main/secondary_view/layouts/bottom_sheet/utils/tab_header.dart @@ -86,7 +86,7 @@ class TabHeader extends StatelessWidget { return Row( children: [ SizedBox(width: calc(40), child: child), - SizedBox(width: calc(16)), + SizedBox(width: calc(8)), Text( title, style: Theme.of(context).textTheme.titleLarge,