diff --git a/packages/smooth_app/assets/fonts/SmoothIcons.ttf b/packages/smooth_app/assets/fonts/SmoothIcons.ttf index b2259c6ae55d..86d9285620ff 100644 Binary files a/packages/smooth_app/assets/fonts/SmoothIcons.ttf and b/packages/smooth_app/assets/fonts/SmoothIcons.ttf differ diff --git a/packages/smooth_app/assets/fonts/icons/config.json b/packages/smooth_app/assets/fonts/icons/config.json index 297587673406..4d5f4f78c3ad 100644 --- a/packages/smooth_app/assets/fonts/icons/config.json +++ b/packages/smooth_app/assets/fonts/icons/config.json @@ -1677,6 +1677,20 @@ "search": [ "image_error" ] + }, + { + "uid": "d9b51ca84fa9ddc8ad93d480ffa74b81", + "css": "milk_download", + "code": 59511, + "src": "custom_icons", + "selected": true, + "svg": { + "path": "M538.5 0C554.7 0 567.8 13.1 567.8 29.3L567.8 61.4 654 233.8 657.5 241.4C662.6 254.2 665.4 267.9 665.4 281.8L665.4 502.3C645 505 625.4 510.1 606.8 517.2L606.8 281.8C606.8 274.3 605.1 266.8 601.7 260L538.5 133.7 475.4 260C472 266.8 470.2 274.2 470.2 281.8L470.2 638.2C450.9 674.6 440 716 440 760 440 773.6 441 787 443.1 800L148.3 800C110.9 800 80 769.1 80 731.7L80 281.8C80 265.2 83.9 248.8 91.3 233.8L177.6 61.4 177.6 29.3C177.6 13.1 190.7 0 206.8 0L538.5 0ZM202.2 509.6C190.2 509.9 179.5 517.5 175.4 528.9 171.3 540.2 174.6 552.9 183.7 560.9 234.5 607.9 312.5 607.9 363.3 560.9 372.3 552.8 375.4 540 371.1 528.7 366.8 517.4 356.1 509.9 344 509.8 336.4 509.8 329 512.7 323.5 517.9 293.5 545.7 253.4 545.7 223.4 517.9 217.8 512.4 210.1 509.4 202.2 509.6ZM206.8 409.8C190.7 409.8 177.6 422.9 177.6 439 177.6 455.2 190.7 468.3 206.8 468.3 223 468.3 236.1 455.2 236.1 439 236.1 422.9 223 409.8 206.8 409.8ZM343.4 409.8C327.2 409.8 314.1 422.9 314.1 439 314.1 455.2 327.2 468.3 343.4 468.3 359.6 468.3 372.7 455.2 372.7 439 372.7 422.9 359.6 409.8 343.4 409.8ZM491.2 97.6L224.9 97.6 156.6 234.1 422.9 234.1 491.2 97.6ZM480 760C480 638.5 578.5 540 700 540 821.5 540 920 638.5 920 760 920 881.5 821.5 980 700 980 578.5 980 480 881.5 480 760ZM713 864.6L786.3 791.3C789.9 787.7 791.7 783 791.7 778.3 791.7 773.6 789.9 768.9 786.3 765.4 779.1 758.2 767.5 758.2 760.4 765.4L718.3 807.4 718.3 668.3C718.3 658.2 710.1 650 700 650 689.9 650 681.7 658.2 681.7 668.3L681.7 807.4 639.6 765.4C632.5 758.2 620.9 758.2 613.7 765.4 606.5 772.5 606.5 784.1 613.7 791.3L687 864.6C694.2 871.8 705.8 871.8 713 864.6Z", + "width": 1000 + }, + "search": [ + "milk_download" + ] } ] } \ No newline at end of file diff --git a/packages/smooth_app/assets/fonts/icons/icons.sketch b/packages/smooth_app/assets/fonts/icons/icons.sketch index 339876b2d19c..bb2a210bc739 100644 Binary files a/packages/smooth_app/assets/fonts/icons/icons.sketch and b/packages/smooth_app/assets/fonts/icons/icons.sketch differ diff --git a/packages/smooth_app/assets/fonts/icons/milk_download.svg b/packages/smooth_app/assets/fonts/icons/milk_download.svg new file mode 100644 index 000000000000..4bfd176f7349 --- /dev/null +++ b/packages/smooth_app/assets/fonts/icons/milk_download.svg @@ -0,0 +1,8 @@ + + + Milk extract + + + + + \ No newline at end of file diff --git a/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart b/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart index 778098081f73..701147bcaf03 100644 --- a/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart +++ b/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart @@ -10,6 +10,7 @@ import 'package:smooth_app/themes/smooth_theme.dart'; import 'package:smooth_app/themes/smooth_theme_colors.dart'; import 'package:smooth_app/themes/theme_provider.dart'; import 'package:smooth_app/widgets/smooth_barcode_widget.dart'; +import 'package:smooth_app/widgets/smooth_close_button.dart'; import 'package:smooth_app/widgets/smooth_text.dart'; /// A common Widget for carrousel item cards. @@ -191,36 +192,12 @@ class _SmoothProductCardHeader extends StatelessWidget { ), if (onClose != null) ...[ const SizedBox(width: MEDIUM_SPACE), - Semantics( - label: closeTooltip, - button: true, - excludeSemantics: true, - child: Tooltip( - message: closeTooltip, - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: onClose, - customBorder: const CircleBorder(), - child: Padding( - padding: const EdgeInsetsDirectional.all(11.0), - child: Ink( - width: 28.0, - height: 28.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - ), - padding: const EdgeInsetsDirectional.all(7.0), - child: icons.Close( - size: 14.0, - color: bgColor, - ), - ), - ), - ), - ), - ), + SmoothCloseButton( + onClose: onClose!, + circleColor: bgColor, + crossColor: Colors.white, + padding: const EdgeInsetsDirectional.all(11.0), + tooltip: closeTooltip, ), ], ], diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 869a016f1e05..67f7ea27f501 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -749,6 +749,10 @@ "nutrition_page_invalid_number": "Invalid number", "nutrition_page_update_running": "Updating the product on the server…", "nutrition_page_update_done": "Product updated!", + "nutrition_page_take_serving_size_from_product_quantity": "Use the product quantity as serving size", + "@nutrition_page_take_serving_size_from_product_quantity": { + "description": "Button label: Use the product quantity as serving size (nutrition page)" + }, "more_photos": "More interesting photos", "@more_photos": {}, "view_more_photo_button": "View all existing photos for this product", @@ -2673,6 +2677,10 @@ "@owner_field_info_message": { "description": "Title of the 'producer provided' info list-tile" }, + "owner_field_info_close_button": "Close this info", + "@owner_field_info_close_button": { + "description": "The owner info may be shown in a closeable dialog. This is the label of the button (used on a long press event and for the accessibility label)." + }, "edit_packagings_title": "Packaging components", "@edit_packagings_title": { "description": "Title of the structured packagings page" diff --git a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart index a34997d4eb3a..fe85792a6122 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -24,6 +24,7 @@ import 'package:smooth_app/pages/product/owner_field_info.dart'; import 'package:smooth_app/pages/product/simple_input_number_field.dart'; import 'package:smooth_app/pages/text_field_helper.dart'; import 'package:smooth_app/query/product_query.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; import 'package:smooth_app/widgets/smooth_scaffold.dart'; import 'package:smooth_app/widgets/smooth_switch.dart'; import 'package:smooth_app/widgets/will_pop_scope.dart'; @@ -88,6 +89,10 @@ class _NutritionPageLoadedState extends State with UpToDateMixin { late final NumberFormat _decimalNumberFormat; late final NutritionContainer _nutritionContainer; + bool _ownerFieldBannerVisible = false; + + /// When the banner is visible, we add a padding to the list + double _ownerFieldBannerHeight = 0.0; final Map _controllers = {}; @@ -98,14 +103,18 @@ class _NutritionPageLoadedState extends State @override void initState() { super.initState(); - initUpToDate(widget.product, context.read()); + initUpToDate( + widget.product, + context.read(), + ); _nutritionContainer = NutritionContainer( orderedNutrients: widget.orderedNutrients, product: upToDateProduct, ); - _decimalNumberFormat = - SimpleInputNumberField.getNumberFormat(decimal: true); + _decimalNumberFormat = SimpleInputNumberField.getNumberFormat( + decimal: true, + ); } @override @@ -126,6 +135,80 @@ class _NutritionPageLoadedState extends State context.watch(); refreshUpToDate(); + final List children = _generateListOfWidgets( + appLocalizations, + context, + ); + + return WillPopScope2( + onWillPop: () async => (await _mayExitPage(saving: false), null), + child: Provider<_NutritionPageLoadedState>.value( + value: this, + child: SmoothScaffold( + fixKeyboard: true, + appBar: buildEditProductAppBar( + context: context, + title: appLocalizations.nutrition_page_title, + product: upToDateProduct, + ), + body: Stack( + children: [ + Positioned.fill( + child: Padding( + padding: const EdgeInsetsDirectional.symmetric( + horizontal: LARGE_SPACE, + ), + child: Form( + key: _formKey, + child: Provider>.value( + value: _focusNodes, + child: ListView( + padding: const EdgeInsetsDirectional.symmetric( + vertical: SMALL_SPACE, + ), + children: children, + ), + ), + ), + ), + ), + Positioned( + bottom: 0.0, + left: 0.0, + right: 0.0, + child: AnimatedOwnerFieldBanner( + visible: _ownerFieldBannerVisible, + shadow: true, + onHeightChanged: (double height) { + _ownerFieldBannerHeight = height; + if (_ownerFieldBannerVisible) { + setState(() {}); + } + }, + onDismissClicked: () { + setState(() => _ownerFieldBannerVisible = false); + }, + ), + ), + ], + ), + bottomNavigationBar: ProductBottomButtonsBar( + onSave: () async => _exitPage( + await _mayExitPage(saving: true), + ), + onCancel: () async => _exitPage( + await _mayExitPage(saving: false), + ), + ), + ), + ), + ); + } + + List _generateListOfWidgets( + AppLocalizations appLocalizations, + BuildContext context, + ) { final List children = []; // List of focus nodes for all text fields except the serving one. @@ -146,9 +229,6 @@ class _NutritionPageLoadedState extends State ), ), ); - if (_hasOwnerField(displayableNutrients)) { - children.add(const OwnerFieldInfo()); - } children.add(_getServingField(appLocalizations)); children.add(_getServingSwitch(appLocalizations)); @@ -195,70 +275,14 @@ class _NutritionPageLoadedState extends State refreshParent: () => setState(() {}), ), ); - } else { - _focusNodes.clear(); - } - return WillPopScope2( - onWillPop: () async => (await _mayExitPage(saving: false), null), - child: SmoothScaffold( - fixKeyboard: true, - appBar: buildEditProductAppBar( - context: context, - title: appLocalizations.nutrition_page_title, - product: upToDateProduct, - ), - body: Padding( - padding: const EdgeInsetsDirectional.symmetric( - horizontal: LARGE_SPACE, - ), - child: Form( - key: _formKey, - child: Provider>.value( - value: _focusNodes, - child: ListView( - padding: const EdgeInsetsDirectional.symmetric( - vertical: SMALL_SPACE, - ), - children: children, - ), - ), - ), - ), - bottomNavigationBar: ProductBottomButtonsBar( - onSave: () async => _exitPage( - await _mayExitPage(saving: true), - ), - onCancel: () async => _exitPage( - await _mayExitPage(saving: false), - ), - ), - ), - ); - } - - bool _hasOwnerField( - final Iterable displayableNutrients, - ) { - if (upToDateProduct.getOwnerFieldTimestamp( - OwnerField.productField( - ProductField.SERVING_SIZE, - ProductQuery.getLanguage(), - ), - ) != - null) { - return true; - } - for (final OrderedNutrient orderedNutrient in displayableNutrients) { - final Nutrient nutrient = _getNutrient(orderedNutrient); - if (upToDateProduct.getOwnerFieldTimestamp( - OwnerField.nutrient(nutrient), - ) != - null) { - return true; + if (_ownerFieldBannerVisible) { + children.add(SizedBox(height: _ownerFieldBannerHeight)); } + } else { + _focusNodes.clear(); } - return false; + return children; } Widget _getServingField(final AppLocalizations appLocalizations) { @@ -281,15 +305,7 @@ class _NutritionPageLoadedState extends State decoration: InputDecoration( enabledBorder: const UnderlineInputBorder(), labelText: appLocalizations.nutrition_page_serving_size, - suffixIcon: widget.product.getOwnerFieldTimestamp( - OwnerField.productField( - ProductField.SERVING_SIZE, - ProductQuery.getLanguage(), - ), - ) == - null - ? null - : const OwnerFieldIcon(), + suffixIcon: _getServingFieldLeading(appLocalizations), ), textInputAction: TextInputAction.next, onFieldSubmitted: (_) { @@ -310,6 +326,38 @@ class _NutritionPageLoadedState extends State ); } + Widget? _getServingFieldLeading(final AppLocalizations appLocalizations) { + if (widget.product.getOwnerFieldTimestamp(OwnerField.productField( + ProductField.SERVING_SIZE, + ProductQuery.getLanguage(), + )) != + null) { + return IconButton( + onPressed: () { + context.read<_NutritionPageLoadedState>().toggleOwnerFieldBanner(); + }, + icon: const OwnerFieldIcon(), + ); + } + + if (_servingController?.initialValue?.isEmpty == true && + _servingController?.text.isEmpty == true && + widget.product.quantity != null) { + return IconButton( + onPressed: () { + _servingController!.text = widget.product.quantity!; + }, + icon: const icons.Milk.download(), + visualDensity: VisualDensity.compact, + enableFeedback: true, + tooltip: appLocalizations + .nutrition_page_take_serving_size_from_product_quantity, + ); + } + + return null; + } + Widget _getServingSwitch(final AppLocalizations appLocalizations) => IntrinsicHeight( child: Row( @@ -512,6 +560,12 @@ class _NutritionPageLoadedState extends State ); return true; } + + void toggleOwnerFieldBanner() { + setState(() { + _ownerFieldBannerVisible = !_ownerFieldBannerVisible; + }); + } } class _NutrientRow extends StatelessWidget { @@ -604,7 +658,14 @@ class _NutrientValueCell extends StatelessWidget { product.getOwnerFieldTimestamp(OwnerField.nutrient(nutrient)) == null ? null - : const OwnerFieldIcon(), + : IconButton( + onPressed: () { + context + .read<_NutritionPageLoadedState>() + .toggleOwnerFieldBanner(); + }, + icon: const OwnerFieldIcon(), + ), ), keyboardType: const TextInputType.numberWithOptions( signed: false, diff --git a/packages/smooth_app/lib/pages/product/owner_field_info.dart b/packages/smooth_app/lib/pages/product/owner_field_info.dart index 2912545a7584..995505738c8b 100644 --- a/packages/smooth_app/lib/pages/product/owner_field_info.dart +++ b/packages/smooth_app/lib/pages/product/owner_field_info.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/widgets/smooth_banner.dart'; +import 'package:smooth_app/widgets/widget_height.dart'; /// Icon to display when the product field value is "producer provided". const IconData _ownerFieldIconData = Icons.factory; @@ -25,7 +27,16 @@ class OwnerFieldInfo extends StatelessWidget { } class OwnerFieldBanner extends StatelessWidget { - const OwnerFieldBanner({super.key}); + const OwnerFieldBanner({ + this.shadow = false, + this.onDismissClicked, + super.key, + }); + + final bool shadow; + + /// If not null, a dismiss button is displayed + final ValueChanged? onDismissClicked; @override Widget build(BuildContext context) { @@ -35,6 +46,8 @@ class OwnerFieldBanner extends StatelessWidget { icon: const OwnerFieldIcon(), title: appLocalizations.owner_field_info_title, content: appLocalizations.owner_field_info_message, + topShadow: shadow, + onDismissClicked: onDismissClicked, ); } } @@ -44,8 +57,110 @@ class OwnerFieldIcon extends StatelessWidget { const OwnerFieldIcon(); @override - Widget build(BuildContext context) => Semantics( - label: AppLocalizations.of(context).owner_field_info_title, - child: const Icon(_ownerFieldIconData), + Widget build(BuildContext context) => Icon( + _ownerFieldIconData, + semanticLabel: AppLocalizations.of(context).owner_field_info_title, ); } + +class AnimatedOwnerFieldBanner extends StatefulWidget { + const AnimatedOwnerFieldBanner({ + required this.visible, + this.onHeightChanged, + this.onDismissClicked, + this.shadow, + super.key, + }); + + final bool visible; + final ValueChanged? onHeightChanged; + final bool? shadow; + + /// If not null, a dismiss button is displayed + final VoidCallback? onDismissClicked; + + @override + State createState() => + _AnimatedOwnerFieldBannerState(); +} + +class _AnimatedOwnerFieldBannerState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + double? _height; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: SmoothAnimationsDuration.short, + )..addListener(() => setState(() {})); + + _animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeInSine, + reverseCurve: Curves.easeInCubic, + ), + ); + + if (widget.visible) { + _controller.value = 1.0; + } else { + _controller.value = 0.0; + } + } + + @override + void didUpdateWidget(covariant AnimatedOwnerFieldBanner oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.visible != widget.visible) { + if (widget.visible) { + _controller.forward(); + } else { + _controller.reverse(); + } + } + } + + @override + Widget build(BuildContext context) { + return Offstage( + offstage: _animation.value == 0.0, + child: Transform.translate( + offset: Offset(0.0, (_height ?? 0.0) * (1 - _animation.value)), + child: MeasureSize( + onChange: (Size size) { + if (_height == null || _height! < size.height) { + _height = size.height; + } + widget.onHeightChanged?.call(size.height); + }, + child: OwnerFieldBanner( + shadow: widget.shadow ?? false, + onDismissClicked: _animation.value > 0.0 + ? (SmoothBannerDismissEvent event) { + /// From the button, we still need to animate + _controller.reverse( + from: event == SmoothBannerDismissEvent.fromButton + ? 1.0 + : 0.0, + ); + widget.onDismissClicked?.call(); + } + : null, + ), + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/packages/smooth_app/lib/resources/app_icons.dart b/packages/smooth_app/lib/resources/app_icons.dart index 7744c952be68..c2af1a4dc498 100644 --- a/packages/smooth_app/lib/resources/app_icons.dart +++ b/packages/smooth_app/lib/resources/app_icons.dart @@ -968,6 +968,14 @@ class Milk extends AppIcon { super.semanticLabel, super.key, }) : super._(_IconsFont.milk_filled); + + const Milk.download({ + super.color, + super.size, + super.shadow, + super.semanticLabel, + super.key, + }) : super._(_IconsFont.milk_download); } class NoPicture extends AppIcon { diff --git a/packages/smooth_app/lib/resources/app_icons_font.dart b/packages/smooth_app/lib/resources/app_icons_font.dart index 850e8c73836f..381ab6fdfbf0 100644 --- a/packages/smooth_app/lib/resources/app_icons_font.dart +++ b/packages/smooth_app/lib/resources/app_icons_font.dart @@ -239,6 +239,8 @@ class _IconsFont { IconData(0xe875, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData image_error = IconData(0xe876, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData milk_download = + IconData(0xe877, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData share_cupertino = IconData(0xe8a4, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData share_material = diff --git a/packages/smooth_app/lib/widgets/smooth_banner.dart b/packages/smooth_app/lib/widgets/smooth_banner.dart index 62e09fba7587..085167f3beb1 100644 --- a/packages/smooth_app/lib/widgets/smooth_banner.dart +++ b/packages/smooth_app/lib/widgets/smooth_banner.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/widgets/smooth_close_button.dart'; class SmoothBanner extends StatelessWidget { const SmoothBanner({ required this.icon, required this.title, required this.content, + this.onDismissClicked, + this.topShadow = false, super.key, }); @@ -13,9 +17,15 @@ class SmoothBanner extends StatelessWidget { final String title; final String content; + /// If not null, a dismiss button is displayed + final ValueChanged? onDismissClicked; + final bool topShadow; + + static const Color _titleColor = Color(0xFF373737); + @override Widget build(BuildContext context) { - return IntrinsicHeight( + Widget child = IntrinsicHeight( child: Row( children: [ Expanded( @@ -44,24 +54,47 @@ class SmoothBanner extends StatelessWidget { child: Container( width: double.infinity, color: const Color(0xFFECECEC), - padding: const EdgeInsetsDirectional.only( + padding: EdgeInsetsDirectional.only( start: MEDIUM_SPACE, end: MEDIUM_SPACE, - top: BALANCED_SPACE, - bottom: MEDIUM_SPACE, + top: onDismissClicked != null + ? VERY_SMALL_SPACE + : BALANCED_SPACE, + bottom: onDismissClicked != null ? LARGE_SPACE : MEDIUM_SPACE, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: const TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, - color: Color(0xFF373737), - ), + Row( + children: [ + Expanded( + child: Text( + title, + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + color: _titleColor, + ), + ), + ), + if (onDismissClicked != null) ...[ + const SizedBox(width: SMALL_SPACE), + SmoothCloseButton( + onClose: () => onDismissClicked!.call( + SmoothBannerDismissEvent.fromButton, + ), + circleColor: _titleColor, + crossColor: Colors.white, + circleSize: 26.0, + crossSize: 12.0, + tooltip: AppLocalizations.of(context) + .owner_field_info_close_button, + ), + ], + ], ), - const SizedBox(height: VERY_SMALL_SPACE), + if (onDismissClicked == null) + const SizedBox(height: VERY_SMALL_SPACE), Text( content, style: const TextStyle( @@ -76,5 +109,40 @@ class SmoothBanner extends StatelessWidget { ], ), ); + + if (topShadow) { + child = DecoratedBox( + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 6.0, + offset: Offset(0.0, -4.0), + ), + ], + ), + child: child, + ); + } + + if (onDismissClicked != null) { + child = Dismissible( + key: const Key('SmoothBanner'), + direction: DismissDirection.down, + onDismissed: (DismissDirection direction) { + if (direction == DismissDirection.down) { + onDismissClicked!.call(SmoothBannerDismissEvent.fromSwipe); + } + }, + child: child, + ); + } + + return child; } } + +enum SmoothBannerDismissEvent { + fromSwipe, + fromButton, +} diff --git a/packages/smooth_app/lib/widgets/smooth_close_button.dart b/packages/smooth_app/lib/widgets/smooth_close_button.dart new file mode 100644 index 000000000000..548cd2c0102c --- /dev/null +++ b/packages/smooth_app/lib/widgets/smooth_close_button.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; + +class SmoothCloseButton extends StatelessWidget { + const SmoothCloseButton({ + required this.onClose, + required this.circleColor, + required this.crossColor, + required this.tooltip, + this.padding, + this.circleSize = 28.0, + this.crossSize = 14.0, + super.key, + }) : assert(tooltip.length > 0); + + final VoidCallback onClose; + final Color circleColor; + final Color crossColor; + final String tooltip; + final EdgeInsetsGeometry? padding; + final double? circleSize; + final double? crossSize; + + @override + Widget build(BuildContext context) { + return Semantics( + label: tooltip, + button: true, + excludeSemantics: true, + child: Tooltip( + message: tooltip, + child: Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onClose, + customBorder: const CircleBorder(), + child: Padding( + padding: const EdgeInsetsDirectional.all(SMALL_SPACE), + child: Ink( + width: circleSize, + height: circleSize, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: circleColor, + ), + padding: const EdgeInsetsDirectional.all(7.0), + child: icons.Close( + size: crossSize, + color: crossColor, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/smooth_app/lib/widgets/v2/smooth_buttons_bar.dart b/packages/smooth_app/lib/widgets/v2/smooth_buttons_bar.dart index 07d42d48f472..ac0f03d371e2 100644 --- a/packages/smooth_app/lib/widgets/v2/smooth_buttons_bar.dart +++ b/packages/smooth_app/lib/widgets/v2/smooth_buttons_bar.dart @@ -4,6 +4,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/duration_constants.dart'; +import 'package:smooth_app/helpers/keyboard_helper.dart'; import 'package:smooth_app/themes/smooth_theme_colors.dart'; import 'package:smooth_app/themes/theme_provider.dart'; @@ -112,13 +113,16 @@ class _SmoothButtonsBar2State extends State } double get _bottomPadding { + final double padding; if (Platform.isIOS) { - return 0.0; + padding = 0.0; } else if (Platform.isAndroid) { - return VERY_SMALL_SPACE; + padding = VERY_SMALL_SPACE; } else { - return MEDIUM_SPACE; + padding = MEDIUM_SPACE; } + + return padding + (context.keyboardVisible ? BALANCED_SPACE : 0.0); } @override