From 83427c993918561916ac7dde69c88ac0d1fe45eb Mon Sep 17 00:00:00 2001 From: hieubt Date: Fri, 3 Nov 2023 15:16:04 +0700 Subject: [PATCH 1/7] TF-2196 Add 'From' field text and color --- .../presentation/extensions/color_extension.dart | 2 ++ lib/l10n/intl_messages.arb | 12 ++++++++++++ lib/main/localizations/app_localizations.dart | 14 ++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 43bb2aa454..262e0aac5d 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -227,6 +227,8 @@ extension AppColor on Color { static const colorButtonBorder = Color(0xFFD5D7E0); static const colorScrollbarTrackColor = Color(0xFFF4EFF4); static const colorScrollbarThumbColor = Color(0xFFD8E1EB); + static const colorDropDownTitleComposer = Color(0xFF79747E); + static const colorDropDownItemTitleComposer = Color(0xFF0A0A0A); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 7ff2949e2c..6b92a088f3 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -3415,5 +3415,17 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "yourIdentities": "Your identities", + "@yourIdentities": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "searchForIdentities": "Search for identities", + "@searchForIdentities": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 95a8735e74..d6ad9edc00 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3533,4 +3533,18 @@ class AppLocalizations { name: 'confirmAllEmailHereAreSpam' ); } + + String get yourIdentities { + return Intl.message( + 'Your identities', + name: 'yourIdentities', + ); + } + + String get searchForIdentities { + return Intl.message( + 'Search for identities', + name: 'searchForIdentities', + ); + } } \ No newline at end of file From 0bc7851f4142afa9ea1244bd5c381944edad3abe Mon Sep 17 00:00:00 2001 From: hieubt Date: Fri, 3 Nov 2023 16:05:39 +0700 Subject: [PATCH 2/7] TF-2196 Add 'From'to recipient widget --- .../widgets/recipient_composer_widget.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart index 473e20f98d..ad77746a20 100644 --- a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart @@ -40,6 +40,7 @@ class RecipientComposerWidget extends StatefulWidget { final PrefixEmailAddress prefix; final List listEmailAddress; final ExpandMode expandMode; + final PrefixRecipientState fromState; final PrefixRecipientState ccState; final PrefixRecipientState bccState; final bool? isInitial; @@ -66,6 +67,7 @@ class RecipientComposerWidget extends StatefulWidget { required this.listEmailAddress, this.ccState = PrefixRecipientState.disabled, this.bccState = PrefixRecipientState.disabled, + this.fromState = PrefixRecipientState.disabled, this.isInitial, this.controller, this.focusNode, @@ -301,6 +303,15 @@ class _RecipientComposerWidgetState extends State { ) ), const SizedBox(width: RecipientComposerWidgetStyle.space), + if (widget.prefix == PrefixEmailAddress.to && widget.fromState == PrefixRecipientState.disabled) + TMailButtonWidget.fromText( + text: AppLocalizations.of(context).from_email_address_prefix, + textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, + backgroundColor: Colors.transparent, + padding: RecipientComposerWidgetStyle.prefixButtonPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.from), + ), if (widget.prefix == PrefixEmailAddress.to && widget.ccState == PrefixRecipientState.disabled) TMailButtonWidget.fromText( text: AppLocalizations.of(context).cc_email_address_prefix, From 81c27f0a8593dabdb5a90f58d10afc6266927e58 Mon Sep 17 00:00:00 2001 From: hieubt Date: Fri, 3 Nov 2023 16:06:08 +0700 Subject: [PATCH 3/7] TF-2196 Add 'From' field dropdown --- .../from_composer_drop_down_widget_style.dart | 99 +++++++++ .../web/from_composer_drop_down_widget.dart | 191 ++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 lib/features/composer/presentation/styles/web/from_composer_drop_down_widget_style.dart create mode 100644 lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart diff --git a/lib/features/composer/presentation/styles/web/from_composer_drop_down_widget_style.dart b/lib/features/composer/presentation/styles/web/from_composer_drop_down_widget_style.dart new file mode 100644 index 0000000000..8f76bd34e8 --- /dev/null +++ b/lib/features/composer/presentation/styles/web/from_composer_drop_down_widget_style.dart @@ -0,0 +1,99 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; + +class FromComposerDropDownWidgetStyle { + static const double space = 8.0; + static const double dropdownItemSpace = 12.0; + static const double avatarSize = 48.0; + static const double avatarBorderWidth = 0.5; + static const double dropdownTopBarHeight = 32.0; + static const double buttonHeight = 32.0; + static const double buttonWidth = 411.0; + + static const EdgeInsetsGeometry prefixPadding = EdgeInsetsDirectional.only(top: 16.0); + static const EdgeInsetsGeometry editIdentityIconPadding = EdgeInsets.all(5.0); + static const EdgeInsetsGeometry dropdownButtonPadding = EdgeInsets.symmetric(vertical: 8.0); + static const EdgeInsetsGeometry dropdownItemPadding = EdgeInsets.symmetric(vertical: 12.0); + static const EdgeInsetsGeometry dropdownTopBarPadding = EdgeInsets.only( + top: 12.0, + left: 20.0, + right: 20.0, + ); + static const EdgeInsetsGeometry buttonPadding = EdgeInsets.only( + top: 4.0, + bottom: 4.0, + right: 4.0, + left: 8.0, + ); + + static const editIdentityIconBorderRadius = BorderRadius.all(Radius.circular(12.0)); + + static const BoxDecoration buttonDecoration = BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + color: AppColor.colorComposerAppBar, + ); + + static const ButtonStyleData buttonStyleData = ButtonStyleData( + width: buttonWidth, + height: buttonHeight, + padding: buttonPadding, + decoration: buttonDecoration, + ); + static DropdownStyleData dropdownStyleData = DropdownStyleData( + maxHeight: 272.0, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + color: Colors.white, + ), + width: 381.0, + elevation: 4, + offset: const Offset(0.0, -8.0), + scrollbarTheme: ScrollbarThemeData( + radius: const Radius.circular(40.0), + thickness: MaterialStateProperty.all(6.0), + thumbVisibility: MaterialStateProperty.all(true), + ) + ); + static MenuItemStyleData menuIemStyleData = MenuItemStyleData( + padding: const EdgeInsets.symmetric(horizontal: 12), + height: 72, + overlayColor: MaterialStateProperty.resolveWith((Set states) => Colors.white) + ); + + static const TextStyle prefixTextStyle = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColor.colorLabelComposer, + ); + static const TextStyle avatarTextStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black, + ); + static const TextStyle dropdownItemTitleTextStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.colorDropDownItemTitleComposer, + ); + static const TextStyle dropdownItemSubTitleTextStyle = TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: AppColor.colorLabelQuotas + ); + static const TextStyle dropdownTitleTextStyle = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColor.colorDropDownTitleComposer, + ); + static const TextStyle dropdownButtonTitleTextStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.colorCalendarEventUnread + ); + static const TextStyle dropdownButtonSubTitleTextStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.colorLabelComposer + ); +} \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart b/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart new file mode 100644 index 0000000000..4dbec500b4 --- /dev/null +++ b/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart @@ -0,0 +1,191 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/extensions/string_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:jmap_dart_client/jmap/identities/identity.dart'; +import 'package:model/model.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/styles/web/from_composer_drop_down_widget_style.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnChangeIdentity = void Function(Identity? identity); + +class FromComposerDropDownWidget extends StatelessWidget { + + final List items; + final GlobalKey? dropdownKey; + final Identity? itemSelected; + final EdgeInsetsGeometry? padding; + final EdgeInsetsGeometry? margin; + final OnChangeIdentity? onChangeIdentity; + final ImagePaths imagePaths; + + const FromComposerDropDownWidget({ + super.key, + required this.items, + required this.dropdownKey, + required this.imagePaths, + this.itemSelected, + this.padding, + this.margin, + this.onChangeIdentity, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColor.colorLineComposer, + width: 1, + ) + ) + ), + padding: padding, + margin: margin, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: FromComposerDropDownWidgetStyle.prefixPadding, + child: Text( + '${PrefixEmailAddress.from.asName(context)}:', + style: FromComposerDropDownWidgetStyle.prefixTextStyle, + ), + ), + const SizedBox(width: FromComposerDropDownWidgetStyle.space), + Padding( + padding: FromComposerDropDownWidgetStyle.dropdownButtonPadding, + child: DropdownButtonHideUnderline( + child: PointerInterceptor( + child: DropdownButton2( + key: dropdownKey, + isExpanded: false, + items: items.map((item) => DropdownMenuItem( + value: item, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + padding: FromComposerDropDownWidgetStyle.dropdownItemPadding, + child: Row( + children: [ + Container( + width: FromComposerDropDownWidgetStyle.avatarSize, + height: FromComposerDropDownWidgetStyle.avatarSize, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.avatarColor, + border: Border.all( + color: AppColor.colorShadowBgContentEmail, + width: FromComposerDropDownWidgetStyle.avatarBorderWidth + ) + ), + child: Text( + item.name!.isNotEmpty + ? item.name!.firstLetterToUpperCase + : item.email!.firstLetterToUpperCase, + style: FromComposerDropDownWidgetStyle.avatarTextStyle, + ), + ), + const SizedBox(width: FromComposerDropDownWidgetStyle.dropdownItemSpace), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (item.name!.isNotEmpty) + Text( + item.name!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerDropDownWidgetStyle.dropdownItemTitleTextStyle, + ), + if (item.email!.isNotEmpty) + Text( + item.email!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerDropDownWidgetStyle.dropdownItemSubTitleTextStyle, + ) + ], + ), + ) + ], + ), + ), + ), + )).toList(), + value: itemSelected, + buttonStyleData: FromComposerDropDownWidgetStyle.buttonStyleData, + dropdownSearchData: DropdownSearchData( + searchInnerWidget: Container( + padding: FromComposerDropDownWidgetStyle.dropdownTopBarPadding, + child: Text( + AppLocalizations.of(context).yourIdentities, + style: FromComposerDropDownWidgetStyle.dropdownTitleTextStyle, + ), + ), + searchInnerWidgetHeight: FromComposerDropDownWidgetStyle.dropdownTopBarHeight, + ), + dropdownStyleData: FromComposerDropDownWidgetStyle.dropdownStyleData, + iconStyleData: IconStyleData( + icon: SvgPicture.asset(imagePaths.icDropDown), + ), + menuItemStyleData: FromComposerDropDownWidgetStyle.menuIemStyleData, + customButton: Tooltip( + message: itemSelected != null ? itemSelected!.email : '', + child: Container( + height: FromComposerDropDownWidgetStyle.buttonHeight, + width: FromComposerDropDownWidgetStyle.buttonWidth, + padding: FromComposerDropDownWidgetStyle.buttonPadding, + decoration: FromComposerDropDownWidgetStyle.buttonDecoration, + child: Row( + children: [ + if (itemSelected != null) + Expanded( + child: RichText( + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + text: TextSpan( + children: [ + if (itemSelected!.name!.isNotEmpty) + TextSpan( + text: '${itemSelected!.name} ', + style: FromComposerDropDownWidgetStyle.dropdownButtonTitleTextStyle, + ), + TextSpan( + text: '(${itemSelected!.email})', + style: itemSelected!.name!.isNotEmpty + ? FromComposerDropDownWidgetStyle.dropdownButtonSubTitleTextStyle + : FromComposerDropDownWidgetStyle.dropdownButtonTitleTextStyle + ) + ] + ), + ) + ) + else + const SizedBox.shrink(), + SvgPicture.asset(imagePaths.icDropDown) + ], + ), + ), + ), + onChanged: onChangeIdentity, + ), + ), + ), + ), + ], + ), + ); + } +} \ No newline at end of file From e8d4edad98a31e28e18cc4cd7225469b2e7949d9 Mon Sep 17 00:00:00 2001 From: hieubt Date: Fri, 3 Nov 2023 16:06:30 +0700 Subject: [PATCH 4/7] TF-2196 Add 'From; field fow mobile --- .../from_composer_mobile_widget_style.dart | 44 ++++++++ .../mobile/from_composer_mobile_widget.dart | 100 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart create mode 100644 lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart diff --git a/lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart b/lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart new file mode 100644 index 0000000000..941658d0d4 --- /dev/null +++ b/lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart @@ -0,0 +1,44 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; + +class FromComposerMobileWidgetStyle { + static const double space = 8.0; + static const double identityButtonHeight = 32.0; + static const double identityButtonWidthMobileLandscape = 421.0; + + static const EdgeInsetsGeometry identityButtonInkWellPadding = EdgeInsets.symmetric(vertical: 8.0); + static const EdgeInsetsGeometry identityButtonPadding = EdgeInsets.only( + top: 4, + bottom: 4, + right: 4, + left: 8, + ); + + static const BorderRadius identityButtonInkWellBorderRadius = BorderRadius.all(Radius.circular(10)); + static const BoxDecoration identityButtonDecoration = BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: AppColor.colorComposerAppBar, + ); + static const Border border = Border( + bottom: BorderSide( + color: AppColor.colorLineComposer, + width: 1, + ) + ); + + static const TextStyle prefixTextStyle = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColor.colorLabelComposer + ); + static const TextStyle buttonTitleTextStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.colorCalendarEventUnread + ); + static const TextStyle buttonSubTitleTextStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.colorLabelComposer + ); +} \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart b/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart new file mode 100644 index 0000000000..b46d365e34 --- /dev/null +++ b/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart @@ -0,0 +1,100 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:jmap_dart_client/jmap/identities/identity.dart'; +import 'package:model/email/prefix_email_address.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart'; + +class FromComposerMobileWidget extends StatelessWidget { + + final Identity? selectedIdentity; + final ImagePaths imagePaths; + final ResponsiveUtils responsiveUtils; + final EdgeInsetsGeometry? padding; + final EdgeInsetsGeometry? margin; + final void Function()? onTap; + + const FromComposerMobileWidget({ + super.key, + required this.imagePaths, + required this.responsiveUtils, + this.selectedIdentity, + this.padding, + this.margin, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + border: FromComposerMobileWidgetStyle.border + ), + padding: padding, + margin: margin, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(top: 16), + child: Text( + '${PrefixEmailAddress.from.asName(context)}:', + style: FromComposerMobileWidgetStyle.prefixTextStyle, + ), + ), + const SizedBox(width: FromComposerMobileWidgetStyle.space), + Expanded( + child: Container( + padding: FromComposerMobileWidgetStyle.identityButtonInkWellPadding, + child: InkWell( + borderRadius: FromComposerMobileWidgetStyle.identityButtonInkWellBorderRadius, + onTap: onTap, + child: Container( + height: FromComposerMobileWidgetStyle.identityButtonHeight, + width: responsiveUtils.isLandscapeMobile(context) + ? FromComposerMobileWidgetStyle.identityButtonWidthMobileLandscape + : 0, + padding: FromComposerMobileWidgetStyle.identityButtonPadding, + decoration: FromComposerMobileWidgetStyle.identityButtonDecoration, + child: Row( + children: [ + if (selectedIdentity != null) + Expanded( + child: RichText( + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + text: TextSpan( + children: [ + if (selectedIdentity!.name!.isNotEmpty) + TextSpan( + text: '${selectedIdentity!.name} ', + style: FromComposerMobileWidgetStyle.buttonTitleTextStyle, + ), + TextSpan( + text: '(${selectedIdentity!.email})', + style: selectedIdentity!.name!.isNotEmpty + ? FromComposerMobileWidgetStyle.buttonSubTitleTextStyle + : FromComposerMobileWidgetStyle.buttonTitleTextStyle + ) + ] + ), + ) + ) + else + const SizedBox.shrink(), + SvgPicture.asset(imagePaths.icDropDown) + ], + ), + ), + ), + ), + ), + ], + ), + ); + } +} \ No newline at end of file From 4b96ce5085ca44cc76e9ce3bf38cc6b8249ccfa2 Mon Sep 17 00:00:00 2001 From: hieubt Date: Fri, 3 Nov 2023 16:06:57 +0700 Subject: [PATCH 5/7] TF-2196 Add identities bottom sheet --- .../from_composer_bottom_sheet_style.dart | 57 +++++ .../from_composer_bottom_sheet_builder.dart | 231 ++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 lib/features/composer/presentation/styles/mobile/from_composer_bottom_sheet_style.dart create mode 100644 lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart diff --git a/lib/features/composer/presentation/styles/mobile/from_composer_bottom_sheet_style.dart b/lib/features/composer/presentation/styles/mobile/from_composer_bottom_sheet_style.dart new file mode 100644 index 0000000000..e7f76ede57 --- /dev/null +++ b/lib/features/composer/presentation/styles/mobile/from_composer_bottom_sheet_style.dart @@ -0,0 +1,57 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; + +class FromComposerBottomSheetStyle { + static const double space = 8.0; + static const double searchIconSize = 14.0; + static const double searchBarBottomSpace = 24.0; + static const double scrollbarThickness = 8.0; + static const double identityItemSize = 48.0; + static const double identityItemBorderWidth = 0.5; + static const double identityItemSpace = 12.0; + + static const EdgeInsetsGeometry closeButtonPadding = EdgeInsets.all(16.0); + static const EdgeInsetsGeometry bodyPadding = EdgeInsets.symmetric(horizontal: 12.0); + static const EdgeInsetsGeometry searchTextInputPadding = EdgeInsets.all(12.0); + + static const Color barrierColor = AppColor.colorDefaultCupertinoActionSheet; + static const Color searchIconColor = AppColor.loginTextFieldHintColor; + + static const BorderRadius backgroundBorderRadius = BorderRadius.only( + topLeft: Radius.circular(14), + topRight: Radius.circular(14), + ); + static const Radius radius = Radius.circular(10.0); + static const BorderRadius borderRadius = BorderRadius.all(radius); + + static const BoxDecoration searchBarDecoration = BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: AppColor.colorBgSearchBar + ); + + static const TextStyle appBarTitleTextStyle = TextStyle( + color: Colors.black, + fontWeight: FontWeight.w700, + fontSize: 20.0, + ); + static const TextStyle searchBarTextStyle = TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w600, + ); + static const TextStyle searchHintTextStyle = TextStyle( + color: AppColor.loginTextFieldHintColor, + fontSize: 16.0, + fontWeight: FontWeight.w400, + ); + static const TextStyle identityItemTitleTextStyle = TextStyle( + color: Colors.black, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ); + static const TextStyle identityItemSubTitleTextStyle = TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: AppColor.colorLabelQuotas + ); +} \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart b/lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart new file mode 100644 index 0000000000..f1cb040a90 --- /dev/null +++ b/lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart @@ -0,0 +1,231 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/extensions/string_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'package:core/presentation/views/text/text_field_builder.dart'; +import 'package:core/utils/direction_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/identities/identity.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/composer/presentation/styles/mobile/from_composer_bottom_sheet_style.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnChangeIdentity = void Function(Identity? identity); +typedef OnTextSearchChanged = void Function(String text); + +class FromComposerBottomSheetBuilder { + + final BuildContext _context; + final ImagePaths _imagePaths; + final List _identities; + final ScrollController _scrollController; + final TextEditingController _textInputSearchController; + + late double _statusBarHeight; + + FocusNode? _textInputSearchFocusNode; + OnChangeIdentity? _onChangeIdentity; + VoidCallback? _onClose; + OnTextSearchChanged? _onTextSearchChanged; + + FromComposerBottomSheetBuilder( + this._context, + this._imagePaths, + this._identities, + this._scrollController, + this._textInputSearchController, + ) { + _statusBarHeight = Get.statusBarHeight / MediaQuery.of(_context).devicePixelRatio; + } + + void onChangeIdentityAction(OnChangeIdentity onChangeIdentity) { + _onChangeIdentity = onChangeIdentity; + } + + void onCloseAction(VoidCallback onClose) { + _onClose = onClose; + } + + void onTextSearchChangedAction(OnTextSearchChanged onTextSearchChanged) { + _onTextSearchChanged = onTextSearchChanged; + } + + Future build() { + return showModalBottomSheet( + context: _context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + enableDrag: false, + barrierColor: FromComposerBottomSheetStyle.barrierColor, + builder: (context) { + return Obx(() => PointerInterceptor( + child: SafeArea( + top: true, + bottom: false, + left: false, + right: false, + child: GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Padding( + padding: EdgeInsets.only(top: _statusBarHeight), + child: ClipRRect( + borderRadius: FromComposerBottomSheetStyle.backgroundBorderRadius, + child: Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + leading: const SizedBox.shrink(), + title: Text(AppLocalizations.of(context).yourIdentities), + titleTextStyle: FromComposerBottomSheetStyle.appBarTitleTextStyle, + centerTitle: true, + actions: [ + GestureDetector( + child: Padding( + padding: FromComposerBottomSheetStyle.closeButtonPadding, + child: SvgPicture.asset(_imagePaths.icCircleClose), + ), + onTapDown: (_) { + _onClose?.call(); + }, + ) + ], + ), + body: Padding( + padding: FromComposerBottomSheetStyle.bodyPadding, + child: Column( + children: [ + Container( + decoration: FromComposerBottomSheetStyle.searchBarDecoration, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(width: FromComposerBottomSheetStyle.space), + buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icSearchBar, + fit: BoxFit.fill, + colorFilter: FromComposerBottomSheetStyle.searchIconColor.asFilter(), + ), + iconPadding: EdgeInsets.zero, + iconSize: FromComposerBottomSheetStyle.searchIconSize, + ), + Expanded( + child: TextFieldBuilder( + controller: _textInputSearchController, + focusNode: _textInputSearchFocusNode, + onTextChange: _onTextSearchChanged, + textInputAction: TextInputAction.search, + maxLines: 1, + textDirection: DirectionUtils.getDirectionByLanguage(context), + textStyle: FromComposerBottomSheetStyle.searchBarTextStyle, + keyboardType: TextInputType.text, + decoration: InputDecoration( + contentPadding: FromComposerBottomSheetStyle.searchTextInputPadding, + hintText: AppLocalizations.of(context).searchForIdentities, + hintStyle: FromComposerBottomSheetStyle.searchHintTextStyle, + border: InputBorder.none + ), + ), + ), + ], + ), + ), + const SizedBox(height: FromComposerBottomSheetStyle.searchBarBottomSpace), + Expanded( + child: RawScrollbar( + trackColor: AppColor.colorScrollbarTrackColor, + thumbColor: AppColor.colorScrollbarThumbColor, + radius: FromComposerBottomSheetStyle.radius, + trackRadius: FromComposerBottomSheetStyle.radius, + thickness: FromComposerBottomSheetStyle.scrollbarThickness, + thumbVisibility: true, + trackVisibility: true, + controller: _scrollController, + trackBorderColor: Colors.transparent, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: ListView.builder( + controller: _scrollController, + shrinkWrap: true, + physics: const ScrollPhysics(), + itemCount: _identities.length, + itemBuilder: ((context, index) { + final Identity identity = _identities[index]; + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: FromComposerBottomSheetStyle.borderRadius, + onTap: () => _onChangeIdentity?.call(identity), + child: Padding( + padding: FromComposerBottomSheetStyle.searchTextInputPadding, + child: Row( + children: [ + Container( + width: FromComposerBottomSheetStyle.identityItemSize, + height: FromComposerBottomSheetStyle.identityItemSize, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.avatarColor, + border: Border.all( + color: AppColor.colorShadowBgContentEmail, + width: FromComposerBottomSheetStyle.identityItemBorderWidth + ) + ), + child: Text( + identity.name!.isNotEmpty + ? identity.name!.firstLetterToUpperCase + : identity.email!.firstLetterToUpperCase, + style: FromComposerBottomSheetStyle.searchBarTextStyle, + ), + ), + const SizedBox(width: FromComposerBottomSheetStyle.identityItemSpace), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (identity.name!.isNotEmpty) + Text( + identity.name!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerBottomSheetStyle.identityItemTitleTextStyle, + ), + if (identity.email!.isNotEmpty) + Text( + identity.email!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerBottomSheetStyle.identityItemSubTitleTextStyle, + ) + ], + ), + ) + ], + ), + ), + ), + ); + }), + ), + ), + ) + ), + ], + ), + ), + ), + ), + ), + ) + ) + )); + }, + ); + } +} \ No newline at end of file From cf64aba72cf5e1b6cff99bd6a4ad9573c6073ce7 Mon Sep 17 00:00:00 2001 From: hieubt Date: Fri, 3 Nov 2023 16:07:50 +0700 Subject: [PATCH 6/7] TF-2196 Update composer with 'From' --- .../composer/presentation/composer_view.dart | 23 ++++++++++++ .../presentation/composer_view_web.dart | 37 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 2ed4a9c94b..d29f3fb88a 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -16,6 +16,8 @@ import 'package:tmail_ui_user/features/composer/presentation/styles/mobile_app_b import 'package:tmail_ui_user/features/composer/presentation/view/mobile/mobile_container_view.dart'; import 'package:tmail_ui_user/features/composer/presentation/view/mobile/mobile_editor_view.dart'; import 'package:tmail_ui_user/features/composer/presentation/view/mobile/tablet_container_view.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/insert_image_loading_bar_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/mobile_attachment_composer_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart'; @@ -98,9 +100,19 @@ class ComposerView extends GetWidget { children: [ Obx(() => Column( children: [ + if (controller.fromRecipientState.value == PrefixRecipientState.enabled) + FromComposerMobileWidget( + selectedIdentity: controller.identitySelected.value, + imagePaths: _imagePaths, + responsiveUtils: _responsiveUtils, + margin: ComposerStyle.mobileRecipientMargin, + padding: ComposerStyle.mobileRecipientPadding, + onTap: () => controller.openSelectIdentityBottomSheet(context) + ), RecipientComposerWidget( prefix: PrefixEmailAddress.to, listEmailAddress: controller.listToEmailAddress, + fromState: controller.fromRecipientState.value, ccState: controller.ccRecipientState.value, bccState: controller.bccRecipientState.value, expandMode: controller.toAddressExpandMode.value, @@ -238,9 +250,20 @@ class ComposerView extends GetWidget { children: [ Obx(() => Column( children: [ + if (controller.fromRecipientState.value == PrefixRecipientState.enabled) + FromComposerDropDownWidget( + items: controller.listFromIdentities, + itemSelected: controller.identitySelected.value, + dropdownKey: controller.identityDropdownKey, + imagePaths: _imagePaths, + padding: ComposerStyle.mobileRecipientPadding, + margin: ComposerStyle.mobileRecipientMargin, + onChangeIdentity: controller.onChangeIdentity, + ), RecipientComposerWidget( prefix: PrefixEmailAddress.to, listEmailAddress: controller.listToEmailAddress, + fromState: controller.fromRecipientState.value, ccState: controller.ccRecipientState.value, bccState: controller.bccRecipientState.value, expandMode: controller.toAddressExpandMode.value, diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index f94564bbbe..e724cc6748 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -14,6 +14,8 @@ import 'package:tmail_ui_user/features/composer/presentation/view/web/desktop_re import 'package:tmail_ui_user/features/composer/presentation/view/web/mobile_responsive_container_view.dart'; import 'package:tmail_ui_user/features/composer/presentation/view/web/tablet_responsive_container_view.dart'; import 'package:tmail_ui_user/features/composer/presentation/view/web/web_editor_view.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/insert_image_loading_bar_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/web/desktop_app_bar_composer_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/web/attachment_composer_widget.dart'; @@ -72,9 +74,22 @@ class ComposerView extends GetWidget { controller: controller.scrollControllerEmailAddress, child: Obx(() => Column( children: [ + if (controller.fromRecipientState.value == PrefixRecipientState.enabled) + Tooltip( + message: controller.identitySelected.value?.email ?? '', + child: FromComposerMobileWidget( + selectedIdentity: controller.identitySelected.value, + imagePaths: _imagePaths, + responsiveUtils: _responsiveUtils, + margin: ComposerStyle.mobileRecipientMargin, + padding: ComposerStyle.mobileRecipientPadding, + onTap: () => controller.openSelectIdentityBottomSheet(context) + ), + ), RecipientComposerWidget( prefix: PrefixEmailAddress.to, listEmailAddress: controller.listToEmailAddress, + fromState: controller.fromRecipientState.value, ccState: controller.ccRecipientState.value, bccState: controller.bccRecipientState.value, expandMode: controller.toAddressExpandMode.value, @@ -242,9 +257,20 @@ class ComposerView extends GetWidget { controller: controller.scrollControllerEmailAddress, child: Obx(() => Column( children: [ + if (controller.fromRecipientState.value == PrefixRecipientState.enabled) + FromComposerDropDownWidget( + items: controller.listFromIdentities, + itemSelected: controller.identitySelected.value, + dropdownKey: controller.identityDropdownKey, + imagePaths: _imagePaths, + padding: ComposerStyle.desktopRecipientPadding, + margin: ComposerStyle.desktopRecipientMargin, + onChangeIdentity: controller.onChangeIdentity, + ), RecipientComposerWidget( prefix: PrefixEmailAddress.to, listEmailAddress: controller.listToEmailAddress, + fromState: controller.fromRecipientState.value, ccState: controller.ccRecipientState.value, bccState: controller.bccRecipientState.value, expandMode: controller.toAddressExpandMode.value, @@ -464,9 +490,20 @@ class ComposerView extends GetWidget { controller: controller.scrollControllerEmailAddress, child: Obx(() => Column( children: [ + if (controller.fromRecipientState.value == PrefixRecipientState.enabled) + FromComposerDropDownWidget( + items: controller.listFromIdentities, + itemSelected: controller.identitySelected.value, + dropdownKey: controller.identityDropdownKey, + imagePaths: _imagePaths, + padding: ComposerStyle.tabletRecipientPadding, + margin: ComposerStyle.tabletRecipientMargin, + onChangeIdentity: controller.onChangeIdentity, + ), RecipientComposerWidget( prefix: PrefixEmailAddress.to, listEmailAddress: controller.listToEmailAddress, + fromState: controller.fromRecipientState.value, ccState: controller.ccRecipientState.value, bccState: controller.bccRecipientState.value, expandMode: controller.toAddressExpandMode.value, From ca1cc1c716cb04fc9060cc36595c1917951cca39 Mon Sep 17 00:00:00 2001 From: hieubt Date: Fri, 3 Nov 2023 16:08:36 +0700 Subject: [PATCH 7/7] TF-2196 Update controller with 'From' --- .../presentation/composer_controller.dart | 74 ++++++++++++++++--- .../from_composer_mobile_widget_style.dart | 8 +- .../mobile/from_composer_mobile_widget.dart | 16 ++-- 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 66efbaaee2..cc270b7da5 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -7,6 +7,7 @@ import 'package:collection/collection.dart'; import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:file_picker/file_picker.dart'; import 'package:filesize/filesize.dart'; import 'package:fk_user_agent/fk_user_agent.dart'; @@ -58,6 +59,7 @@ import 'package:tmail_ui_user/features/composer/presentation/model/save_to_draft import 'package:tmail_ui_user/features/composer/presentation/model/save_to_draft_view_event.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_content_state.dart'; @@ -109,9 +111,12 @@ class ComposerController extends BaseController { final bccAddressExpandMode = ExpandMode.EXPAND.obs; final emailContentsViewState = Rxn>(); final hasRequestReadReceipt = false.obs; + final fromRecipientState = PrefixRecipientState.disabled.obs; final ccRecipientState = PrefixRecipientState.disabled.obs; final bccRecipientState = PrefixRecipientState.disabled.obs; final isSendEmailLoading = false.obs; + final identitySelected = Rxn(); + final listFromIdentities = RxList(); final LocalFilePickerInteractor _localFilePickerInteractor; final DeviceInfoPlugin _deviceInfoPlugin; @@ -136,23 +141,27 @@ class ComposerController extends BaseController { final toEmailAddressController = TextEditingController(); final ccEmailAddressController = TextEditingController(); final bccEmailAddressController = TextEditingController(); + final searchIdentitiesInputController = TextEditingController(); final GlobalKey keyToEmailTagEditor = GlobalKey(); final GlobalKey keyCcEmailTagEditor = GlobalKey(); final GlobalKey keyBccEmailTagEditor = GlobalKey(); final GlobalKey headerEditorMobileWidgetKey = GlobalKey(); + final GlobalKey identityDropdownKey = GlobalKey(); final double defaultPaddingCoordinateYCursorEditor = 8; FocusNode? subjectEmailInputFocusNode; FocusNode? toAddressFocusNode; FocusNode? ccAddressFocusNode; FocusNode? bccAddressFocusNode; + FocusNode? searchIdentitiesFocusNode; final RichTextController keyboardRichTextController = RichTextController(); final ScrollController scrollController = ScrollController(); final ScrollController scrollControllerEmailAddress = ScrollController(); final ScrollController scrollControllerAttachment = ScrollController(); + final ScrollController scrollControllerIdentities = ScrollController(); final _saveToDraftEventController = StreamController(); Stream get _saveToDraftEventStream => _saveToDraftEventController.stream; @@ -164,7 +173,6 @@ class ComposerController extends BaseController { double? maxWithEditor; EmailId? _emailIdEditing; bool isAttachmentCollapsed = false; - Identity? identitySelected; late Worker uploadInlineImageWorker; late Worker dashboardViewStateWorker; @@ -227,6 +235,8 @@ class ComposerController extends BaseController { ccAddressFocusNode = null; bccAddressFocusNode?.dispose(); bccAddressFocusNode = null; + searchIdentitiesFocusNode?.dispose(); + searchIdentitiesFocusNode = null; subjectEmailInputController.dispose(); toEmailAddressController.dispose(); ccEmailAddressController.dispose(); @@ -238,6 +248,7 @@ class ComposerController extends BaseController { scrollControllerEmailAddress.removeListener(_scrollControllerEmailAddressListener); scrollControllerEmailAddress.dispose(); scrollControllerAttachment.dispose(); + scrollControllerIdentities.dispose(); _saveToDraftStreamSubscription.cancel(); _saveToDraftEventController.close(); super.dispose(); @@ -366,6 +377,7 @@ class ComposerController extends BaseController { ); ccAddressFocusNode = FocusNode(); bccAddressFocusNode = FocusNode(); + searchIdentitiesFocusNode = FocusNode(); subjectEmailInputFocusNode?.addListener(() { log('ComposerController::createFocusNodeInput():subjectEmailInputFocusNode: ${subjectEmailInputFocusNode?.hasFocus}'); @@ -414,7 +426,7 @@ class ComposerController extends BaseController { } void onLoadCompletedMobileEditorAction(HtmlEditorApi editorApi, WebUri? url) { - if (identitySelected == null) { + if (identitySelected.value == null) { _getAllIdentities(); } } @@ -552,10 +564,13 @@ class ComposerController extends BaseController { void _handleGetAllIdentitiesSuccess(GetAllIdentitiesSuccess success) async { final listIdentitiesMayDeleted = success.identities?.toListMayDeleted() ?? []; if (listIdentitiesMayDeleted.isNotEmpty) { + listFromIdentities.value = listIdentitiesMayDeleted; await _selectIdentity(listIdentitiesMayDeleted.first); } - _autoFocusFieldWhenLauncher(); + if (fromRecipientState.value == PrefixRecipientState.disabled) { + _autoFocusFieldWhenLauncher(); + } } void _initEmailAddress({ @@ -663,17 +678,17 @@ class ComposerController extends BaseController { } ) async { Set listFromEmailAddress = {EmailAddress(null, userProfile.email)}; - if (identitySelected?.email?.isNotEmpty == true) { + if (identitySelected.value?.email?.isNotEmpty == true) { listFromEmailAddress = { EmailAddress( - identitySelected?.name, - identitySelected?.email + identitySelected.value?.name, + identitySelected.value?.email ) }; } Set listReplyToEmailAddress = {EmailAddress(null, userProfile.email)}; - if (identitySelected?.replyTo?.isNotEmpty == true) { - listReplyToEmailAddress = identitySelected!.replyTo!; + if (identitySelected.value?.replyTo?.isNotEmpty == true) { + listReplyToEmailAddress = identitySelected.value!.replyTo!; } final attachments = {}; @@ -931,7 +946,7 @@ class ComposerController extends BaseController { : EmailRequest( email: createdEmail, sentMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleSent], - identityId: identitySelected?.id, + identityId: identitySelected.value?.id, emailIdDestroyed: arguments.emailActionType == EmailActionType.editDraft ? arguments.presentationEmail?.id : null, @@ -1502,6 +1517,9 @@ class ComposerController extends BaseController { void addEmailAddressType(PrefixEmailAddress prefixEmailAddress) { switch(prefixEmailAddress) { + case PrefixEmailAddress.from: + fromRecipientState.value = PrefixRecipientState.enabled; + break; case PrefixEmailAddress.cc: ccRecipientState.value = PrefixRecipientState.enabled; break; @@ -1672,8 +1690,8 @@ class ComposerController extends BaseController { } Future _selectIdentity(Identity? newIdentity) async { - final formerIdentity = identitySelected; - identitySelected = newIdentity; + final formerIdentity = identitySelected.value; + identitySelected.value = newIdentity; if (newIdentity != null) { await _applyIdentityForAllFieldComposer(formerIdentity, newIdentity); } @@ -1927,7 +1945,7 @@ class ComposerController extends BaseController { richTextWebController.editorController.setFullScreen(); onChangeTextEditorWeb(initContent); richTextWebController.setEnableCodeView(); - if (identitySelected == null) { + if (identitySelected.value == null) { _getAllIdentities(); } } @@ -2135,4 +2153,36 @@ class ComposerController extends BaseController { } } } + + Future onChangeIdentity(Identity? newIdentity) async { + await _selectIdentity(newIdentity); + } + + void _searchIdentities(String searchText) { + if (searchText.isEmpty) { + _getAllIdentities(); + } else { + listFromIdentities.value = listFromIdentities + .where((identity) => identity.name?.toLowerCase().contains(searchText.toLowerCase()) == true) + .toList(); + } + } + + void openSelectIdentityBottomSheet(BuildContext context) { + ( + FromComposerBottomSheetBuilder( + context, + _imagePaths, + listFromIdentities, + scrollControllerIdentities, + searchIdentitiesInputController + ) + ..onCloseAction(() => popBack()) + ..onChangeIdentityAction((identity) { + onChangeIdentity(identity); + popBack(); + }) + ..onTextSearchChangedAction((searchText) => _searchIdentities(searchText)) + ).build(); + } } \ No newline at end of file diff --git a/lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart b/lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart index 941658d0d4..0f53b1db30 100644 --- a/lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart +++ b/lib/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart @@ -4,13 +4,11 @@ import 'package:flutter/material.dart'; class FromComposerMobileWidgetStyle { static const double space = 8.0; static const double identityButtonHeight = 32.0; - static const double identityButtonWidthMobileLandscape = 421.0; static const EdgeInsetsGeometry identityButtonInkWellPadding = EdgeInsets.symmetric(vertical: 8.0); static const EdgeInsetsGeometry identityButtonPadding = EdgeInsets.only( top: 4, bottom: 4, - right: 4, left: 8, ); @@ -34,11 +32,13 @@ class FromComposerMobileWidgetStyle { static const TextStyle buttonTitleTextStyle = TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: AppColor.colorCalendarEventUnread + color: AppColor.colorCalendarEventUnread, + overflow: TextOverflow.ellipsis, ); static const TextStyle buttonSubTitleTextStyle = TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: AppColor.colorLabelComposer + color: AppColor.colorLabelComposer, + overflow: TextOverflow.ellipsis, ); } \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart b/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart index b46d365e34..98705e8d99 100644 --- a/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart +++ b/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart @@ -46,26 +46,24 @@ class FromComposerMobileWidget extends StatelessWidget { ), ), const SizedBox(width: FromComposerMobileWidgetStyle.space), - Expanded( - child: Container( + Flexible( + child: Padding( padding: FromComposerMobileWidgetStyle.identityButtonInkWellPadding, child: InkWell( borderRadius: FromComposerMobileWidgetStyle.identityButtonInkWellBorderRadius, onTap: onTap, child: Container( height: FromComposerMobileWidgetStyle.identityButtonHeight, - width: responsiveUtils.isLandscapeMobile(context) - ? FromComposerMobileWidgetStyle.identityButtonWidthMobileLandscape - : 0, padding: FromComposerMobileWidgetStyle.identityButtonPadding, decoration: FromComposerMobileWidgetStyle.identityButtonDecoration, child: Row( + mainAxisSize: MainAxisSize.min, children: [ if (selectedIdentity != null) - Expanded( + Flexible( child: RichText( maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, + softWrap: false, overflow: CommonTextStyle.defaultTextOverFlow, text: TextSpan( children: [ @@ -82,11 +80,11 @@ class FromComposerMobileWidget extends StatelessWidget { ) ] ), - ) + ), ) else const SizedBox.shrink(), - SvgPicture.asset(imagePaths.icDropDown) + SvgPicture.asset(imagePaths.icDropDown), ], ), ),