Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: User lists picker in a modal sheet #4364

Merged
merged 4 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/smooth_app/lib/data_models/product_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,6 @@ class ProductList {
',${country?.offTag ?? ''}';
}
}

bool get isEditable => listType == ProductListType.USER;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:smooth_app/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet_route.dart';
Expand Down Expand Up @@ -27,30 +29,36 @@ Future<T?> showSmoothDraggableModalSheet<T>({

/// You must return a Sliver Widget
required WidgetBuilder bodyBuilder,
double? initHeight,
}) {
return showDraggableModalSheet<T>(
context: context,
borderRadius: const BorderRadius.vertical(top: ROUNDED_RADIUS),
headerBuilder: (_) => header,
headerHeight:
SmoothModalSheetHeader.computeHeight(context, header.closeButton),
headerHeight: header.computeHeight(context),
bodyBuilder: bodyBuilder,
initHeight: initHeight,
);
}

/// A non scrollable modal sheet
class SmoothModalSheet extends StatelessWidget {
const SmoothModalSheet({
required this.title,
SmoothModalSheet({
required String title,
required this.body,
this.closeButton = true,
bool closeButton = true,
this.bodyPadding,
this.closeButtonSemanticsOrder,
});
double? closeButtonSemanticsOrder,
}) : header = SmoothModalSheetHeader(
title: title,
suffix: closeButton
? SmoothModalSheetHeaderCloseButton(
semanticsOrder: closeButtonSemanticsOrder,
)
: null,
);

final String title;
final bool closeButton;
final double? closeButtonSemanticsOrder;
final SmoothModalSheetHeader header;
final Widget body;
final EdgeInsetsGeometry? bodyPadding;

Expand All @@ -65,11 +73,7 @@ class SmoothModalSheet extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SmoothModalSheetHeader(
title: title,
closeButton: closeButton,
closeButtonSemanticsOrder: closeButtonSemanticsOrder,
),
header,
Padding(
padding: bodyPadding ?? const EdgeInsets.all(MEDIUM_SPACE),
child: body,
Expand All @@ -78,81 +82,203 @@ class SmoothModalSheet extends StatelessWidget {
)),
);
}

double computeHeaderHeight(BuildContext context) =>
header.computeHeight(context);
}

class SmoothModalSheetHeader extends StatelessWidget {
class SmoothModalSheetHeader extends StatelessWidget implements SizeWidget {
const SmoothModalSheetHeader({
required this.title,
this.closeButton = true,
this.closeButtonSemanticsOrder,
this.suffix,
});

static const double MIN_HEIGHT = 50.0;

final String title;
final bool closeButton;
final double? closeButtonSemanticsOrder;
final SizeWidget? suffix;

@override
Widget build(BuildContext context) {
final Color primaryColor = Theme.of(context).primaryColor;

return Container(
height: suffix is SmoothModalSheetHeaderButton ? double.infinity : null,
color: primaryColor.withOpacity(0.2),
constraints: const BoxConstraints(minHeight: MIN_HEIGHT),
padding: EdgeInsetsDirectional.only(
start: VERY_LARGE_SPACE,
top: VERY_SMALL_SPACE,
bottom: VERY_SMALL_SPACE,
end: VERY_LARGE_SPACE - (closeButton ? LARGE_SPACE : 0),
end: VERY_LARGE_SPACE -
(suffix?.requiresPadding == true ? 0 : LARGE_SPACE),
),
child: Row(
children: <Widget>[
Expanded(
child: Semantics(
sortKey: const OrdinalSortKey(1.0),
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleLarge,
child: IntrinsicHeight(
child: Row(
children: <Widget>[
Expanded(
child: Semantics(
sortKey: const OrdinalSortKey(1.0),
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
if (suffix != null) suffix!
],
),
),
);
}

double computeHeight(BuildContext context) {
return math.max(
widgetHeight(context),
suffix?.widgetHeight(context) ?? 0.0,
);
}

@override
double widgetHeight(BuildContext context) {
final double size = VERY_SMALL_SPACE * 2 +
(Theme.of(context).textTheme.titleLarge?.fontSize ?? 15.0);

return math.max(MIN_HEIGHT, size);
}

@override
bool get requiresPadding => true;
}

class SmoothModalSheetHeaderButton extends StatelessWidget
implements SizeWidget {
const SmoothModalSheetHeaderButton({
required this.label,
this.prefix,
this.suffix,
this.onTap,
this.tooltip,
});

static const EdgeInsetsGeometry _padding = EdgeInsetsDirectional.symmetric(
horizontal: 15.0,
vertical: 20.0,
);

final String label;
final Widget? prefix;
final Widget? suffix;
final String? tooltip;
final VoidCallback? onTap;

@override
Widget build(BuildContext context) {
return Semantics(
value: tooltip,
button: true,
excludeSemantics: true,
child: Tooltip(
message: tooltip ?? '',
child: TextButton(
onPressed: onTap,
style: TextButton.styleFrom(
padding: _padding,
shape: const RoundedRectangleBorder(
borderRadius: ROUNDED_BORDER_RADIUS,
),
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).primaryColor,
iconColor: Colors.white,
),
if (closeButton)
Semantics(
value: MaterialLocalizations.of(context).closeButtonTooltip,
button: true,
excludeSemantics: true,
onScrollDown: () {},
sortKey: OrdinalSortKey(closeButtonSemanticsOrder ?? 2.0),
child: Tooltip(
message: MaterialLocalizations.of(context).closeButtonTooltip,
enableFeedback: true,
child: InkWell(
onTap: () => Navigator.of(context).pop(),
customBorder: const CircleBorder(),
child: const Padding(
padding: EdgeInsets.all(MEDIUM_SPACE),
child: Icon(Icons.clear),
child: IconTheme(
data: const IconThemeData(
color: Colors.white,
size: 20.0,
),
child: Row(
children: <Widget>[
if (prefix != null) ...<Widget>[
prefix!,
const SizedBox(
width: SMALL_SPACE,
),
],
Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 17.0,
),
maxLines: 1,
),
),
)
],
if (suffix != null) ...<Widget>[
const SizedBox(
width: SMALL_SPACE,
),
suffix!,
],
],
),
),
),
),
);
}

static double computeHeight(
BuildContext context,
bool hasCloseButton,
) {
double size = VERY_SMALL_SPACE * 2;
@override
double widgetHeight(BuildContext context) {
return math.max(17.0 * MediaQuery.textScaleFactorOf(context),
suffix is Icon || prefix is Icon ? 20.0 : 0.0) +
_padding.vertical;
}

if (hasCloseButton == true) {
size += (MEDIUM_SPACE * 2) + (Theme.of(context).iconTheme.size ?? 20.0);
} else {
size += Theme.of(context).textTheme.titleLarge?.fontSize ?? 15.0;
}
@override
bool get requiresPadding => true;
}

class SmoothModalSheetHeaderCloseButton extends StatelessWidget
implements SizeWidget {
const SmoothModalSheetHeaderCloseButton({
this.semanticsOrder,
});

return size;
final double? semanticsOrder;

@override
Widget build(BuildContext context) {
return Semantics(
value: MaterialLocalizations.of(context).closeButtonTooltip,
button: true,
excludeSemantics: true,
sortKey: OrdinalSortKey(semanticsOrder ?? 2.0),
child: Tooltip(
message: MaterialLocalizations.of(context).closeButtonTooltip,
enableFeedback: true,
child: InkWell(
onTap: () => Navigator.of(context).pop(),
customBorder: const CircleBorder(),
child: const Padding(
padding: EdgeInsets.all(MEDIUM_SPACE),
child: Icon(Icons.clear),
),
),
),
);
}

@override
double widgetHeight(BuildContext context) =>
(MEDIUM_SPACE * 2) + (Theme.of(context).iconTheme.size ?? 20.0);

@override
bool get requiresPadding => false;
}

abstract class SizeWidget implements Widget {
double widgetHeight(BuildContext context);

bool get requiresPadding;
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class _SmoothDraggableContentState extends State<_SmoothDraggableContent> {
@override
Widget build(BuildContext context) {
return Scrollbar(
controller: widget.scrollController,
child: CustomScrollView(
cacheExtent: widget.cacheExtent,
key: _contentKey,
Expand All @@ -178,15 +179,18 @@ class _SmoothDraggableContentState extends State<_SmoothDraggableContent> {

/// A fixed header
class _SliverHeader extends SliverPersistentHeaderDelegate {
_SliverHeader({required this.child, required this.height})
: assert(height > 0.0);
_SliverHeader({
required this.child,
required this.height,
}) : assert(height > 0.0);

final Widget child;
final double height;

@override
Widget build(BuildContext context, _, __) {
return child;
// Align is mandatory here (a known-bug in the framework)
return Align(child: child);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,19 +388,22 @@ class _SmoothActionElevatedButton extends StatelessWidget {
value: buttonData.text,
button: true,
excludeSemantics: true,
child: SmoothSimpleButton(
onPressed: buttonData.onPressed,
minWidth: buttonData.minWidth ?? 20.0,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
buttonData.text.toUpperCase(),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: buttonData.lines ?? 2,
style: themeData.textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.bold,
color: buttonData.textColor ?? themeData.colorScheme.onPrimary,
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 30.0),
child: SmoothSimpleButton(
onPressed: buttonData.onPressed,
minWidth: buttonData.minWidth ?? 20.0,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
buttonData.text.toUpperCase(),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: buttonData.lines ?? 2,
style: themeData.textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.bold,
color: buttonData.textColor ?? themeData.colorScheme.onPrimary,
),
),
),
),
Expand Down Expand Up @@ -444,6 +447,7 @@ class _SmoothActionFlatButton extends StatelessWidget {
padding: const EdgeInsets.symmetric(
horizontal: SMALL_SPACE,
),
minimumSize: const Size(0, 50.0),
),
child: SizedBox(
height: buttonData.lines != null
Expand Down
Loading