From 879692aa540503234576245b83e6bcac1f052a2d Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 9 Feb 2022 10:49:27 +0700 Subject: [PATCH] TF-248 Change floating button compose email --- assets/images/ic_compose.svg | 3 + core/lib/core.dart | 1 + .../presentation/resources/image_paths.dart | 1 + .../scrolling_floating_button_animated.dart | 176 ++++++++++++++++++ .../thread/presentation/thread_view.dart | 17 +- lib/main/localizations/app_localizations.dart | 6 + 6 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 assets/images/ic_compose.svg create mode 100644 core/lib/presentation/views/floating_button/scrolling_floating_button_animated.dart diff --git a/assets/images/ic_compose.svg b/assets/images/ic_compose.svg new file mode 100644 index 000000000..04b5da5b3 --- /dev/null +++ b/assets/images/ic_compose.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/lib/core.dart b/core/lib/core.dart index 970a3d7d4..bcbe072bb 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -41,6 +41,7 @@ export 'presentation/views/dialog/loading_dialog_builder.dart'; export 'presentation/views/dialog/downloading_file_dialog_builder.dart'; export 'presentation/views/background/background_widget_builder.dart'; export 'presentation/views/html_viewer/html_content_viewer_widget.dart'; +export 'presentation/views/floating_button/scrolling_floating_button_animated.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 160d0c545..36b91564f 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -50,6 +50,7 @@ class ImagePaths { String get icChevronDown => _getImagePath('ic_chevron_down.svg'); String get icFilter => _getImagePath('ic_filter.svg'); String get icSearchBar => _getImagePath('ic_search_bar.svg'); + String get icCompose => _getImagePath('ic_compose.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/views/floating_button/scrolling_floating_button_animated.dart b/core/lib/presentation/views/floating_button/scrolling_floating_button_animated.dart new file mode 100644 index 000000000..b6a480a36 --- /dev/null +++ b/core/lib/presentation/views/floating_button/scrolling_floating_button_animated.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'dart:math' as math; + +/// Widget to animate the button when scroll down +class ScrollingFloatingButtonAnimated extends StatefulWidget { + /// Function to use when press the button + final GestureTapCallback? onPress; + + /// Double value to set the button elevation + final double? elevation; + + /// Double value to set the button width + final double? width; + + /// Double value to set the button height + final double? height; + + /// Value to set the duration for animation + final Duration? duration; + + /// Widget to use as button icon + final Widget? icon; + + /// Widget to use as button text when button is expanded + final Widget? text; + + /// Value to set the curve for animation + final Curve? curve; + + /// ScrollController to use to determine when user is on top or not + final ScrollController? scrollController; + + /// Double value to set the boundary value when scroll animation is triggered + final double? limitIndicator; + + /// Color to set the button background color + final Color? color; + + /// Value to indicate if animate or not the icon + final bool? animateIcon; + + ScrollingFloatingButtonAnimated( + {Key? key, + required this.icon, + required this.text, + required this.onPress, + required this.scrollController, + this.elevation = 5.0, + this.width = 120.0, + this.height = 60.0, + this.duration = const Duration(milliseconds: 250), + this.curve, + this.limitIndicator = 10.0, + this.color = Colors.blueAccent, + this.animateIcon = true}) + : super(key: key); + + @override + _ScrollingFloatingButtonAnimatedState createState() => + _ScrollingFloatingButtonAnimatedState(); +} + +class _ScrollingFloatingButtonAnimatedState + extends State + with SingleTickerProviderStateMixin { + /// Boolean value to indicate when scroll view is on top + bool _onTop = true; + + /// Value to indicate when to show the child widget + bool _visibleText = false; + + /// Controller for icon animation + late AnimationController _animationController; + + @override + void initState() { + _animationController = new AnimationController( + vsync: this, + duration: Duration(milliseconds: 250), + reverseDuration: Duration(milliseconds: 250), + animationBehavior: AnimationBehavior.normal, + lowerBound: 0, + upperBound: 120, + ); + super.initState(); + _handleScroll(); + } + + @override + void dispose() { + widget.scrollController!.removeListener(() {}); + _animationController.dispose(); + super.dispose(); + } + + /// Function to add listener for scroll + void _handleScroll() { + ScrollController _scrollController = widget.scrollController!; + _scrollController.addListener(() { + print(_scrollController.position.userScrollDirection); + if (_scrollController.position.pixels > widget.limitIndicator! && + _scrollController.position.userScrollDirection == + ScrollDirection.reverse) { + if (widget.animateIcon!) _animationController.forward(); + setState(() { + _onTop = false; + }); + } else if (_scrollController.position.pixels <= widget.limitIndicator! && + _scrollController.position.userScrollDirection == + ScrollDirection.forward) { + if (widget.animateIcon!) _animationController.reverse(); + setState(() { + _onTop = true; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return Card( + elevation: widget.elevation, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(widget.height! / 2))), + child: AnimatedContainer( + curve: widget.curve ?? Curves.easeInOut, + duration: widget.duration!, + height: widget.height, + width: _onTop ? widget.width : widget.height, + onEnd: () { + setState(() { + _visibleText = !_visibleText; + }); + }, + decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(widget.height! / 2))), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(widget.height! / 2)), + onTap: widget.onPress, + child: Ink( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(widget.height! / 2)), + color: widget.color), + child: Row( + mainAxisAlignment: _onTop ? MainAxisAlignment.spaceEvenly : MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 16, right: _onTop ? 10 : 16), + child: AnimatedBuilder( + child: widget.icon!, + animation: _animationController, + builder: (BuildContext context, Widget? _widget) { + return Transform.rotate( + angle: (_animationController.value * 3 * math.pi) / 180, + child: _widget!, + ); + }), + ), + ...(_onTop + ? [ + Expanded( + child: AnimatedOpacity( + opacity: !_visibleText ? 1 : 0, + duration: const Duration(milliseconds: 100), + child: widget.text!, + ), + ) + ] + : []), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 65765f168..9fb2e425e 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -68,11 +68,20 @@ class ThreadView extends GetWidget { ) ), floatingActionButton: Obx(() => !controller.isSearchActive() - ? FloatingActionButton( + ? ScrollingFloatingButtonAnimated( + icon: SvgPicture.asset(imagePaths.icCompose, width: 20, height: 20, fit: BoxFit.fill), + text: Padding( + padding: EdgeInsets.only(right: 10), + child: Text( + AppLocalizations.of(context).compose, + style: TextStyle(color: AppColor.colorTextButton, fontSize: 15.0, fontWeight: FontWeight.w500)) + ), + onPress: () => controller.composeEmailAction(), + scrollController: controller.listEmailController, + color: Colors.white, elevation: 4.0, - child: new Icon(Icons.add), - backgroundColor: AppColor.appColor, - onPressed: () => controller.composeEmailAction()) + width: 140, + animateIcon: false) : SizedBox.shrink()) ), ); diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index f28a5d296..aec86df02 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -470,5 +470,11 @@ class AppLocalizations { 'Search for emails and files', name: 'hint_search_emails'); } + + String get compose { + return Intl.message( + 'Compose', + name: 'compose'); + } }