From c5aa9f7d69ad083f0f6df632cbef1b54606229a5 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 30 May 2022 16:23:33 +0700 Subject: [PATCH] TF-475 Create custom toast --- core/lib/core.dart | 2 + core/lib/presentation/utils/app_toast.dart | 63 +++-- .../presentation/utils/responsive_utils.dart | 9 + .../presentation/views/toast/tmail_toast.dart | 219 ++++++++++++++++++ .../views/toast/toast_position.dart | 29 +++ core/pubspec.yaml | 3 - .../email/presentation/email_controller.dart | 3 +- .../mailbox_dashboard_controller.dart | 6 +- .../presentation/thread_controller.dart | 6 +- 9 files changed, 312 insertions(+), 28 deletions(-) create mode 100644 core/lib/presentation/views/toast/tmail_toast.dart create mode 100644 core/lib/presentation/views/toast/toast_position.dart diff --git a/core/lib/core.dart b/core/lib/core.dart index b3fb31444..941aa289a 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -63,6 +63,8 @@ export 'presentation/views/search/search_bar_view.dart'; export 'presentation/views/popup_menu/popup_menu_item_widget.dart'; export 'presentation/views/tab_bar/custom_tab_indicator.dart'; export 'presentation/views/quick_search/quick_search_input_form.dart'; +export 'presentation/views/toast/toast_position.dart'; +export 'presentation/views/toast/tmail_toast.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/presentation/utils/app_toast.dart b/core/lib/presentation/utils/app_toast.dart index f7fc02b8a..2ae9e3b6c 100644 --- a/core/lib/presentation/utils/app_toast.dart +++ b/core/lib/presentation/utils/app_toast.dart @@ -1,9 +1,7 @@ import 'package:core/core.dart'; -import 'package:core/presentation/extensions/color_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:getwidget/getwidget.dart'; import 'package:get/get.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; @@ -40,24 +38,49 @@ class AppToast { gravity: ToastGravity.BOTTOM); } - void showToastWithAction(BuildContext context, String message, String actionName, Function onActionClick) { - GFToast.showToast( - message, - context, - toastPosition: GFToastPosition.BOTTOM, - textStyle: TextStyle(fontSize: 16, color: Colors.white), - backgroundColor: AppColor.toastWithActionBackgroundColor, - trailing: PointerInterceptor(child: GFButton( - onPressed: () { - ToastView.dismiss(); - onActionClick(); - }, - text: actionName, - type: GFButtonType.transparent, - color: AppColor.buttonActionToastWithActionColor, - )), - toastBorderRadius: 5.0, - toastDuration: 3 + void showToastWithAction( + BuildContext context, + String message, + String actionName, + Function onActionClick, {double? maxWidth}) { + showToastMessage( + context, + message, + maxWidth: maxWidth, + trailing: TextButton( + onPressed: () { + ToastView.dismiss(); + onActionClick.call(); + }, + child: Text( + actionName, + style: const TextStyle(fontSize: 16, color: AppColor.buttonActionToastWithActionColor), + ), + )); + } + + void showToastMessage(BuildContext context, String message, { + Widget? leading, Widget? trailing, double? maxWidth + }) { + TMailToast.showToast( + message, + context, + width: maxWidth, + toastPosition: ToastPosition.BOTTOM, + textStyle: TextStyle(fontSize: 16, color: Colors.white), + backgroundColor: AppColor.toastWithActionBackgroundColor, + trailing: trailing != null + ? Padding( + padding: const EdgeInsets.only(left: 8), + child: PointerInterceptor(child: trailing)) + : null, + leading: leading != null + ? Padding( + padding: const EdgeInsets.only(right: 8), + child: PointerInterceptor(child: leading)) + : null, + toastBorderRadius: 5.0, + toastDuration: 3 ); } diff --git a/core/lib/presentation/utils/responsive_utils.dart b/core/lib/presentation/utils/responsive_utils.dart index fb188d877..59db920e1 100644 --- a/core/lib/presentation/utils/responsive_utils.dart +++ b/core/lib/presentation/utils/responsive_utils.dart @@ -57,4 +57,13 @@ class ResponsiveUtils { bool isHeightShortest(BuildContext context) { return MediaQuery.of(context).size.shortestSide < heightShortest; } + + double getMaxWidthToast(BuildContext context) { + final widthScreen = getSizeScreenWidth(context); + if (isPortraitMobile(context)) { + return widthScreen; + } else { + return widthScreen < 444 ? widthScreen : 444; + } + } } \ No newline at end of file diff --git a/core/lib/presentation/views/toast/tmail_toast.dart b/core/lib/presentation/views/toast/tmail_toast.dart new file mode 100644 index 000000000..a832227c6 --- /dev/null +++ b/core/lib/presentation/views/toast/tmail_toast.dart @@ -0,0 +1,219 @@ +import 'dart:async'; + +import 'package:core/presentation/views/toast/toast_position.dart'; +import 'package:flutter/material.dart'; + +class TMailToast { + /// text of type [String] display on toast + String? text; + + /// defines the duration of time toast display over screen + int? toastDuration; + + /// defines the position of toast over the screen + ToastPosition? toastPosition; + + /// defines the background color of the toast + Color? backgroundColor; + + /// defines the test style of the toast text + TextStyle? textStyle; + + /// defines the border radius of the toast + double? toastBorderRadius; + + /// defines the border of the toast + Border? border; + + /// defines the trailing widget of the toast + Widget? trailing; + + /// defines the leading widget of the toast + Widget? leading; + + /// defines the size width widget of the toast + double? width; + + // ignore: type_annotate_public_apis, always_declare_return_types + static showToast( + text, + BuildContext context, { + toastDuration, + toastPosition, + backgroundColor = const Color(0xAA000000), + textStyle = const TextStyle(fontSize: 15, color: Colors.white), + toastBorderRadius = 20.0, + border, + trailing, + leading, + width, + }) { + assert(text != null); + ToastView.dismiss(); + ToastView.createView(text, context, toastDuration, toastPosition, + backgroundColor, textStyle, toastBorderRadius, border, trailing, + leading, width); + } +} + +class ToastView { + static final ToastView _instance = ToastView._internal(); + // ignore: sort_constructors_first + factory ToastView() => _instance; + // ignore: sort_constructors_first + ToastView._internal(); + + static OverlayState? overlayState; + static OverlayEntry? _overlayEntry; + static bool _isVisible = false; + + // ignore: avoid_void_async + static void createView( + String text, + BuildContext context, + int? toastDuration, + ToastPosition? toastPosition, + Color backgroundColor, + TextStyle textStyle, + double toastBorderRadius, + Border? border, + Widget? trailing, + Widget? leading, + double? width) async { + overlayState = Overlay.of(context, rootOverlay: false); + + final Widget toastChild = ToastCard( + Container( + width: width, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(toastBorderRadius), + border: border, + ), + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + child: trailing == null && leading == null + ? Text(text, softWrap: true, style: textStyle) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (leading != null) leading, + Expanded(child: Text(text, style: textStyle)), + if (trailing != null) trailing + ], + ), + ), + Duration(seconds: toastDuration ?? 2), fadeDuration: 500); + + _overlayEntry = OverlayEntry( + builder: (BuildContext context) => + _showWidgetBasedOnPosition(toastChild, toastPosition)); + + _isVisible = true; + overlayState!.insert(_overlayEntry!); + await Future.delayed(Duration(seconds: toastDuration ?? 2)); + await dismiss(); + } + + static Positioned _showWidgetBasedOnPosition( + Widget child, ToastPosition? toastPosition) { + switch (toastPosition) { + case ToastPosition.BOTTOM: + return Positioned(bottom: 60, left: 18, right: 18, child: child); + case ToastPosition.BOTTOM_LEFT: + return Positioned(bottom: 60, left: 18, child: child); + case ToastPosition.BOTTOM_RIGHT: + return Positioned(bottom: 60, right: 18, child: child); + case ToastPosition.CENTER: + return Positioned( + top: 60, bottom: 60, left: 18, right: 18, child: child); + case ToastPosition.CENTER_LEFT: + return Positioned(top: 60, bottom: 60, left: 18, child: child); + case ToastPosition.CENTER_RIGHT: + return Positioned(top: 60, bottom: 60, right: 18, child: child); + case ToastPosition.TOP_LEFT: + return Positioned(top: 110, left: 18, child: child); + case ToastPosition.TOP_RIGHT: + return Positioned(top: 110, right: 18, child: child); + default: + return Positioned(top: 110, left: 18, right: 18, child: child); + } + } + + static Future dismiss() async { + if (!_isVisible) { + return; + } + _isVisible = false; + _overlayEntry?.remove(); + } +} + +class ToastCard extends StatefulWidget { + const ToastCard(this.child, this.duration, + {Key? key, this.fadeDuration = 500}) + : super(key: key); + + final Widget child; + final Duration duration; + final int fadeDuration; + + @override + ToastStateFulState createState() => ToastStateFulState(); +} + +class ToastStateFulState extends State + with SingleTickerProviderStateMixin { + void showAnimation() { + _animationController!.forward(); + } + + void hideAnimation() { + _animationController!.reverse(); + _timer?.cancel(); + } + + AnimationController? _animationController; + late Animation _fadeAnimation; + + Timer? _timer; + + @override + void initState() { + _animationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: widget.fadeDuration), + ); + _fadeAnimation = + CurvedAnimation(parent: _animationController!, curve: Curves.easeIn); + super.initState(); + + showAnimation(); + _timer = Timer(widget.duration, hideAnimation); + } + + @override + void deactivate() { + _timer?.cancel(); + _animationController!.stop(); + super.deactivate(); + } + + @override + void dispose() { + _timer?.cancel(); + _animationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => FadeTransition( + opacity: _fadeAnimation as Animation, + child: Center( + child: Material( + color: Colors.transparent, + child: widget.child, + ), + ), + ); +} diff --git a/core/lib/presentation/views/toast/toast_position.dart b/core/lib/presentation/views/toast/toast_position.dart new file mode 100644 index 000000000..16a36a3c9 --- /dev/null +++ b/core/lib/presentation/views/toast/toast_position.dart @@ -0,0 +1,29 @@ + +enum ToastPosition { + /// [ToastPosition.TOP] is used to show toast top of the screen + TOP, + + /// [ToastPosition.BOTTOM] is used to show toast bottom of the screen + BOTTOM, + + /// [ToastPosition.CENTER] is used to show toast center of the screen + CENTER, + + /// [ToastPosition.TOP_LEFT] is used to show toast top left of the screen + TOP_LEFT, + + /// [ToastPosition.TOP_RIGHT] is used to show toast top right of the screen + TOP_RIGHT, + + /// [ToastPosition.BOTTOM_LEFT] is used to show toast bottom left of the screen + BOTTOM_LEFT, + + /// [ToastPosition.BOTTOM_RIGHT] is used to show toast bottom right of the screen + BOTTOM_RIGHT, + + /// [ToastPosition.CENTER_LEFT] is used to show toast center left of the screen + CENTER_LEFT, + + /// [ToastPosition.CENTER_RIGHT] is used to show toast center right of the screen + CENTER_RIGHT, +} \ No newline at end of file diff --git a/core/pubspec.yaml b/core/pubspec.yaml index 230fb69c3..8361a9182 100644 --- a/core/pubspec.yaml +++ b/core/pubspec.yaml @@ -58,9 +58,6 @@ dependencies: # device_info_plus device_info_plus: 3.2.2 - # getwidget - getwidget: 2.0.4 - # webview_flutter webview_flutter: 3.0.0 diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index 3d06c7087..0cb8c52db 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -392,7 +392,8 @@ class EmailController extends BaseController { success.currentMailboxId, MoveAction.undo, success.emailActionType)); - } + }, + maxWidth: responsiveUtils.getMaxWidthToast(currentContext!) ); } } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart index 7dfcda945..2e9e98d3d 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart @@ -328,7 +328,8 @@ class MailboxDashBoardController extends ReloadableController { currentOverlayContext!, AppLocalizations.of(currentContext!).drafts_saved, AppLocalizations.of(currentContext!).discard, - () => _discardEmail(success.emailAsDrafts) + () => _discardEmail(success.emailAsDrafts), + maxWidth: _responsiveUtils.getMaxWidthToast(currentContext!) ); } } @@ -349,7 +350,8 @@ class MailboxDashBoardController extends ReloadableController { success.currentMailboxId, MoveAction.undo, success.emailActionType)); - } + }, + maxWidth: _responsiveUtils.getMaxWidthToast(currentContext!) ); } } diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index a16920edb..6d9ef36ff 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -669,7 +669,8 @@ class ThreadController extends BaseController { emailActionType!, destinationPath: destinationPath)); } - } + }, + maxWidth: _responsiveUtils.getMaxWidthToast(currentContext!) ); } @@ -1043,7 +1044,8 @@ class ThreadController extends BaseController { success.currentMailboxId, MoveAction.undo, success.emailActionType)); - } + }, + maxWidth: _responsiveUtils.getMaxWidthToast(currentContext!) ); } }