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');
+ }
}