From 265284a301a315c3cf07f17b617b756b1074b63c Mon Sep 17 00:00:00 2001 From: Faucon <49079695+FauconSpartiate@users.noreply.github.com> Date: Tue, 27 Feb 2024 00:05:03 +0100 Subject: [PATCH] Make BottomSheets scrollable --- lib/ui/widgets/bottom_sheets.dart | 269 ++++++++++++++++++------------ lib/ui/widgets/misc_widgets.dart | 53 ++++++ pubspec.lock | 120 ++++++++----- pubspec.yaml | 2 + 4 files changed, 289 insertions(+), 155 deletions(-) diff --git a/lib/ui/widgets/bottom_sheets.dart b/lib/ui/widgets/bottom_sheets.dart index 3d462b8..14ac22e 100644 --- a/lib/ui/widgets/bottom_sheets.dart +++ b/lib/ui/widgets/bottom_sheets.dart @@ -3,6 +3,7 @@ import "package:flutter/material.dart"; // Package imports: import "package:flex_color_picker/flex_color_picker.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; // Project imports: import "package:graded/localization/translations.dart"; @@ -12,13 +13,13 @@ import "package:graded/ui/utilities/app_theme.dart"; import "package:graded/ui/utilities/custom_icons.dart"; import "package:graded/ui/utilities/haptics.dart"; import "package:graded/ui/utilities/misc_utilities.dart"; +import "package:graded/ui/widgets/misc_widgets.dart"; -class EasyBottomSheet extends StatefulWidget { +class EasyBottomSheet extends StatefulHookWidget { final String title; final IconData? icon; final Widget child; final String? action; - final double bottomPadding; const EasyBottomSheet({ super.key, @@ -26,7 +27,6 @@ class EasyBottomSheet extends StatefulWidget { required this.child, this.icon, this.action, - this.bottomPadding = 20, }); @override @@ -42,40 +42,84 @@ class _EasyBottomSheetState extends State { @override Widget build(BuildContext context) { - return SafeArea( - top: false, - maintainBottomViewPadding: true, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Center( - child: Column( - children: [ - Icon( - widget.icon, - size: 32, - ), - const Padding(padding: EdgeInsets.only(top: 8, bottom: 8)), - Text( - widget.title, - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - const Padding(padding: EdgeInsets.only(bottom: 8)), - ], + final height = useState(0.0); + + final children = [ + Center( + child: Column( + children: [ + Icon( + widget.icon, + size: 32, + ), + const Padding(padding: EdgeInsets.only(top: 8, bottom: 8)), + Text( + widget.title, + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, ), + const Padding(padding: EdgeInsets.only(bottom: 8)), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + child: widget.child, + ), + ]; + + // calculate the proportion + final double maxSize = useMemoized( + () => ((height.value / MediaQuery.of(context).size.height) + 0.06).clamp(.1, .9), + [height.value], + ); + + // for some reason initSize can't equal max size for very long widgets + final initSize = maxSize * (1 - .0001); + + // render the child to get its height + if (height.value == 0) { + return SingleChildScrollView( + child: MeasuredWidget( + onCalculateSize: (v) => height.value = v!.height, + child: Column( + children: children, ), - Padding( - padding: const EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 32), - child: widget.child, + ), + ); + } + + return DraggableScrollableSheet( + expand: false, + maxChildSize: maxSize, + initialChildSize: initSize, + builder: (context, scrollController) { + return SafeArea( + top: false, + maintainBottomViewPadding: true, + child: NotificationListener( + onNotification: (OverscrollIndicatorNotification overscroll) { + overscroll.disallowIndicator(); + return true; + }, + child: Theme( + data: Theme.of(context).copyWith( + cardTheme: Theme.of(context).cardTheme.copyWith(elevation: 0.75), + ), + child: ListView( + controller: scrollController, + children: children, + ), + ), ), - ], - ), + ); + }, ); } } void showColorBottomSheet(BuildContext context, void Function()? onChanged) => showModalBottomSheet( + isScrollControlled: true, context: context, showDragHandle: true, builder: (context) { @@ -96,99 +140,102 @@ class ColorBottomSheet extends StatelessWidget { return EasyBottomSheet( title: translations.colorOther, icon: Icons.color_lens_outlined, - child: SettingsContainer( - children: [ - SwitchSettingsTile( - title: translations.dynamic_color, - settingKey: "dynamic_color", - subtitle: !AppTheme.hasDynamicColor ? translations.no_dynamic_color : "", - defaultValue: AppTheme.hasDynamicColor, - enabled: AppTheme.hasDynamicColor, - onChange: (_) => onChanged?.call(), - ), - SimpleSettingsTile( - title: translations.custom_color, - subtitle: translations.edit_primary_color, - enabled: !AppTheme.hasDynamicColor || !getPreference("dynamic_color"), - trailing: Padding( - padding: const EdgeInsets.only(right: 8), - child: SizedBox( - height: 40, - width: 40, - child: Container( - decoration: BoxDecoration( - color: Color(getPreference("custom_color")), - shape: BoxShape.circle, + child: Card( + child: SettingsContainer( + children: [ + SwitchSettingsTile( + title: translations.dynamic_color, + settingKey: "dynamic_color", + subtitle: !AppTheme.hasDynamicColor ? translations.no_dynamic_color : "", + defaultValue: AppTheme.hasDynamicColor, + enabled: AppTheme.hasDynamicColor, + onChange: (_) => onChanged?.call(), + ), + SimpleSettingsTile( + title: translations.custom_color, + subtitle: translations.edit_primary_color, + enabled: !AppTheme.hasDynamicColor || !getPreference("dynamic_color"), + trailing: Padding( + padding: const EdgeInsets.only(right: 8), + child: SizedBox( + height: 40, + width: 40, + child: Container( + decoration: BoxDecoration( + color: Color(getPreference("custom_color")), + shape: BoxShape.circle, + ), ), ), ), - ), - onTap: () { - lightHaptics(); + onTap: () { + lightHaptics(); - Color selectedColor = Color(getPreference("custom_color")); + Color selectedColor = Color(getPreference("custom_color")); - ColorPicker( - color: selectedColor, - showColorCode: true, - heading: Text( - translations.select_color, - style: Theme.of(context).textTheme.titleLarge, - ), - colorCodeHasColor: true, - enableShadesSelection: false, - selectedPickerTypeColor: Theme.of(context).colorScheme.primary, - enableTonalPalette: true, - wheelHasBorder: true, - tonalSubheading: Padding( - padding: const EdgeInsets.only(top: 8), - child: Text(translations.material_3_shades), - ), - copyPasteBehavior: const ColorPickerCopyPasteBehavior( - copyFormat: ColorPickerCopyFormat.numHexRRGGBB, - ), - spacing: 8, - runSpacing: 8, - columnSpacing: 16, - wheelSquareBorderRadius: 16, - borderRadius: 16, - colorCodePrefixStyle: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.5)), - pickersEnabled: const { - ColorPickerType.primary: true, - ColorPickerType.wheel: true, - ColorPickerType.accent: false, - }, - pickerTypeLabels: { - ColorPickerType.primary: translations.preset, - ColorPickerType.wheel: translations.custom, - }, - onColorChanged: (Color value) { - selectedColor = value; - }, - ) - .showPickerDialog( - context, - ) - .then((_) { - setPreference("custom_color", selectedColor.value); - onChanged?.call(); - }); - }, - ), - SwitchSettingsTile( - title: translations.amoled_mode, - settingKey: "amoled", - // ignore: avoid_redundant_argument_values - defaultValue: false, - onChange: (_) => onChanged?.call(), - ), - ], + ColorPicker( + color: selectedColor, + showColorCode: true, + heading: Text( + translations.select_color, + style: Theme.of(context).textTheme.titleLarge, + ), + colorCodeHasColor: true, + enableShadesSelection: false, + selectedPickerTypeColor: Theme.of(context).colorScheme.primary, + enableTonalPalette: true, + wheelHasBorder: true, + tonalSubheading: Padding( + padding: const EdgeInsets.only(top: 8), + child: Text(translations.material_3_shades), + ), + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyFormat: ColorPickerCopyFormat.numHexRRGGBB, + ), + spacing: 8, + runSpacing: 8, + columnSpacing: 16, + wheelSquareBorderRadius: 16, + borderRadius: 16, + colorCodePrefixStyle: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.5)), + pickersEnabled: const { + ColorPickerType.primary: true, + ColorPickerType.wheel: true, + ColorPickerType.accent: false, + }, + pickerTypeLabels: { + ColorPickerType.primary: translations.preset, + ColorPickerType.wheel: translations.custom, + }, + onColorChanged: (Color value) { + selectedColor = value; + }, + ) + .showPickerDialog( + context, + ) + .then((_) { + setPreference("custom_color", selectedColor.value); + onChanged?.call(); + }); + }, + ), + SwitchSettingsTile( + title: translations.amoled_mode, + settingKey: "amoled", + // ignore: avoid_redundant_argument_values + defaultValue: false, + onChange: (_) => onChanged?.call(), + ), + ], + ), ), ); } } void showSocialsBottomSheet(BuildContext context) => showModalBottomSheet( + isScrollControlled: true, context: context, showDragHandle: true, builder: (context) { diff --git a/lib/ui/widgets/misc_widgets.dart b/lib/ui/widgets/misc_widgets.dart index f852d32..31dc75e 100644 --- a/lib/ui/widgets/misc_widgets.dart +++ b/lib/ui/widgets/misc_widgets.dart @@ -3,6 +3,7 @@ import "package:flutter/material.dart"; // Package imports: import "package:fading_edge_scrollview/fading_edge_scrollview.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_svg/svg.dart"; // Project imports: @@ -191,3 +192,55 @@ class SpinningIcon extends StatelessWidget { ); } } + +class MeasuredWidget extends StatefulWidget { + final Function(Size? size) onCalculateSize; + final Widget child; + + const MeasuredWidget({ + super.key, + required this.onCalculateSize, + required this.child, + }); + + @override + _MeasuredWidgetState createState() => _MeasuredWidgetState(); +} + +class _MeasuredWidgetState extends State { + final key = GlobalKey(); + + @override + void initState() { + //calling the getHeight Function after the Layout is Rendered + WidgetsBinding.instance.addPostFrameCallback((_) => getHeight()); + + super.initState(); + } + + void getHeight() { + final size = key.currentContext?.size; + widget.onCalculateSize(size); + } + + @override + Widget build(BuildContext context) { + return _MeasuredWidgetContent( + key: key, + child: widget.child, + ); + } +} + +class _MeasuredWidgetContent extends HookWidget { + final Widget child; + const _MeasuredWidgetContent({ + super.key, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return child; + } +} diff --git a/pubspec.lock b/pubspec.lock index 353ba15..45264fa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" animations: dependency: "direct main" description: @@ -157,10 +157,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6" + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" url: "https://pub.dev" source: hosted - version: "9.1.1" + version: "9.1.2" device_info_plus_platform_interface: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: "direct main" description: name: fading_edge_scrollview - sha256: bebff5b4551c021c484783a47ec9242aedc881c0c6991bb471c73f85e5694fd0 + sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.1" fake_async: dependency: transitive description: @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -262,6 +262,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_hooks: + dependency: "direct main" + description: + name: flutter_hooks + sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + url: "https://pub.dev" + source: hosted + version: "0.20.5" flutter_localizations: dependency: "direct main" description: flutter @@ -271,18 +279,18 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: "9cdb5d9665dab5d098dc50feab74301c2c228cd02ca25c9b546ab572cebcd6af" + sha256: "558f10070f03ee71f850a78f7136ab239a67636a294a44a06b6b7345178edb1e" url: "https://pub.dev" source: hosted - version: "2.3.9" + version: "2.3.10" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: transitive description: flutter @@ -353,10 +361,10 @@ packages: dependency: transitive description: name: image - sha256: "004a2e90ce080f8627b5a04aecb4cdfac87d2c3f3b520aa291260be5a32c033d" + sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "4.1.7" import_sorter: dependency: "direct dev" description: @@ -397,6 +405,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lint: dependency: "direct dev" description: @@ -417,26 +449,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: @@ -489,10 +521,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -814,26 +846,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.2.5" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.2.5" url_launcher_linux: dependency: transitive description: @@ -854,10 +886,10 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" url_launcher_web: dependency: transitive description: @@ -878,26 +910,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -926,18 +958,18 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.4.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.3" webkit_inspection_protocol: dependency: transitive description: @@ -987,5 +1019,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0-279.1.beta <4.0.0" + flutter: ">=3.16.6" diff --git a/pubspec.yaml b/pubspec.yaml index 40f182f..5f97274 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: sdk: flutter flutter_displaymode: ^0.6.0 flutter_file_dialog: ^3.0.0 + flutter_hooks: ^0.20.5 flutter_localizations: sdk: flutter flutter_svg: ^2.0.0 @@ -127,3 +128,4 @@ import_sorter: ignored_files: - \/lib\\localization\\.* - \/lib\\ui\\utilities\\custom_icons.dart +