Skip to content

Commit

Permalink
Make BottomSheets scrollable
Browse files Browse the repository at this point in the history
  • Loading branch information
FauconSpartiate committed Feb 26, 2024
1 parent 362131f commit 265284a
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 155 deletions.
269 changes: 158 additions & 111 deletions lib/ui/widgets/bottom_sheets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -12,21 +13,20 @@ 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,
required this.title,
required this.child,
this.icon,
this.action,
this.bottomPadding = 20,
});

@override
Expand All @@ -42,40 +42,84 @@ class _EasyBottomSheetState extends State<EasyBottomSheet> {

@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<OverscrollIndicatorNotification>(
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) {
Expand All @@ -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<bool>("dynamic_color"),
trailing: Padding(
padding: const EdgeInsets.only(right: 8),
child: SizedBox(
height: 40,
width: 40,
child: Container(
decoration: BoxDecoration(
color: Color(getPreference<int>("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<bool>("dynamic_color"),
trailing: Padding(
padding: const EdgeInsets.only(right: 8),
child: SizedBox(
height: 40,
width: 40,
child: Container(
decoration: BoxDecoration(
color: Color(getPreference<int>("custom_color")),
shape: BoxShape.circle,
),
),
),
),
),
onTap: () {
lightHaptics();
onTap: () {
lightHaptics();

Color selectedColor = Color(getPreference<int>("custom_color"));
Color selectedColor = Color(getPreference<int>("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, bool>{
ColorPickerType.primary: true,
ColorPickerType.wheel: true,
ColorPickerType.accent: false,
},
pickerTypeLabels: <ColorPickerType, String>{
ColorPickerType.primary: translations.preset,
ColorPickerType.wheel: translations.custom,
},
onColorChanged: (Color value) {
selectedColor = value;
},
)
.showPickerDialog(
context,
)
.then((_) {
setPreference<int>("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, bool>{
ColorPickerType.primary: true,
ColorPickerType.wheel: true,
ColorPickerType.accent: false,
},
pickerTypeLabels: <ColorPickerType, String>{
ColorPickerType.primary: translations.preset,
ColorPickerType.wheel: translations.custom,
},
onColorChanged: (Color value) {
selectedColor = value;
},
)
.showPickerDialog(
context,
)
.then((_) {
setPreference<int>("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) {
Expand Down
53 changes: 53 additions & 0 deletions lib/ui/widgets/misc_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<MeasuredWidget> {
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;
}
}
Loading

0 comments on commit 265284a

Please sign in to comment.