diff --git a/core/lib/presentation/views/text/text_field_builder.dart b/core/lib/presentation/views/text/text_field_builder.dart index d80225d62..8d1cb3d1b 100644 --- a/core/lib/presentation/views/text/text_field_builder.dart +++ b/core/lib/presentation/views/text/text_field_builder.dart @@ -12,6 +12,7 @@ class TextFieldBuilder { InputDecoration? _inputDecoration; bool? _obscureText; int? _maxLines = 1; + int? _minLines; TextEditingController? _textController; TextInputType? _keyboardType; Color? _cursorColor; @@ -58,6 +59,10 @@ class TextFieldBuilder { _maxLines = value; } + void minLines(int? value) { + _minLines = value; + } + void keyboardType(TextInputType? value) { _keyboardType = value; } @@ -84,6 +89,7 @@ class TextFieldBuilder { textInputAction: _textInputAction, decoration: _inputDecoration, maxLines: _maxLines, + minLines: _minLines, keyboardAppearance: Brightness.light, style: _textStyle ?? TextStyle(color: AppColor.textFieldTextColor), obscureText: _obscureText ?? false, diff --git a/lib/features/identity_creator/presentation/identity_creator_controller.dart b/lib/features/identity_creator/presentation/identity_creator_controller.dart index 81d9b272c..e829b1d4e 100644 --- a/lib/features/identity_creator/presentation/identity_creator_controller.dart +++ b/lib/features/identity_creator/presentation/identity_creator_controller.dart @@ -1,5 +1,6 @@ import 'package:core/core.dart'; +import 'package:enough_html_editor/enough_html_editor.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:html_editor_enhanced/html_editor.dart'; @@ -40,6 +41,7 @@ class IdentityCreatorController extends BaseController { final TextEditingController inputNameIdentityController = TextEditingController(); final FocusNode inputNameIdentityFocusNode = FocusNode(); + HtmlEditorApi? signatureHtmlEditorMobileController; AccountId? accountId; UserProfile? userProfile; Identity? identity; @@ -187,7 +189,13 @@ class IdentityCreatorController extends BaseController { } } - void selectSignatureType(SignatureType newSignatureType) { + void selectSignatureType(BuildContext context, SignatureType newSignatureType) async { + if (newSignatureType == SignatureType.plainText) { + final signatureText = await _getSignatureHtmlText(); + log('IdentityCreatorController::selectSignatureType(): signatureText: $signatureText'); + updateContentHtmlEditor(signatureText); + } + clearFocusEditor(context); signatureType.value = newSignatureType; } @@ -203,6 +211,14 @@ class IdentityCreatorController extends BaseController { bccOfIdentity.value = newEmailAddress; } + Future _getSignatureHtmlText() async { + if (BuildUtils.isWeb) { + return await signatureHtmlEditorController.getText(); + } else { + return await signatureHtmlEditorMobileController?.getText(); + } + } + void createNewIdentity(BuildContext context) async { final error = _getErrorInputNameString(context); if (error?.isNotEmpty == true) { @@ -211,6 +227,10 @@ class IdentityCreatorController extends BaseController { return; } + final signatureHtmlText = await _getSignatureHtmlText(); + + log('IdentityCreatorController::createNewIdentity(): signatureHtmlText: $signatureHtmlText'); + final newIdentity = Identity( name: _nameIdentity, email: emailOfIdentity.value?.email, @@ -221,12 +241,12 @@ class IdentityCreatorController extends BaseController { ? {bccOfIdentity.value!} : null, textSignature: Signature(signaturePlainEditorController.text), - htmlSignature: Signature(contentHtmlEditor ?? '')); + htmlSignature: Signature(signatureHtmlText ?? '')); log('IdentityCreatorController::createNewIdentity(): $newIdentity'); _clearAll(); - FocusScope.of(context).unfocus(); + clearFocusEditor(context); popBack(result: newIdentity); } @@ -251,9 +271,16 @@ class IdentityCreatorController extends BaseController { inputNameIdentityController.clear(); } + void clearFocusEditor(BuildContext context) { + if (!BuildUtils.isWeb) { + signatureHtmlEditorMobileController?.unfocus(context); + } + FocusScope.of(context).unfocus(); + } + void closeView(BuildContext context) { _clearAll(); - FocusScope.of(context).unfocus(); + clearFocusEditor(context); popBack(); } } \ No newline at end of file diff --git a/lib/features/identity_creator/presentation/identity_creator_view.dart b/lib/features/identity_creator/presentation/identity_creator_view.dart index 10e950e1d..9a3c13452 100644 --- a/lib/features/identity_creator/presentation/identity_creator_view.dart +++ b/lib/features/identity_creator/presentation/identity_creator_view.dart @@ -1,8 +1,9 @@ import 'package:core/core.dart'; +import 'package:enough_html_editor/enough_html_editor.dart' as html_editor_mobile; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; -import 'package:html_editor_enhanced/html_editor.dart'; +import 'package:html_editor_enhanced/html_editor.dart' as html_editor_browser; import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart'; import 'package:tmail_ui_user/features/identity_creator/presentation/model/signature_type.dart'; import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_drop_list_field_builder.dart'; @@ -24,77 +25,92 @@ class IdentityCreatorView extends GetWidget { responsiveUtils: _responsiveUtils, mobile: Scaffold( backgroundColor: Colors.white, - body: SafeArea( - child: ClipRRect( - borderRadius: const BorderRadius.only(topRight: Radius.circular(14), topLeft: Radius.circular(14)), - child: _buildBodyMobile(context) + body: GestureDetector( + onTap: () => controller.clearFocusEditor(context), + child: SafeArea( + child: ClipRRect( + borderRadius: const BorderRadius.only(topRight: Radius.circular(14), topLeft: Radius.circular(14)), + child: _buildBodyMobile(context) + ), ), ) ), landscapeMobile: Scaffold( backgroundColor: Colors.white, - body: SafeArea( - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.zero), - child: _buildBodyMobile(context) + body: GestureDetector( + onTap: () => controller.clearFocusEditor(context), + child: SafeArea( + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.zero), + child: _buildBodyMobile(context) + ), ), ) ), tablet: Scaffold( backgroundColor: Colors.black38, - body: Align(alignment: Alignment.center, child: Card( - color: Colors.transparent, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(16))), - width: _responsiveUtils.getSizeScreenWidth(context) * 0.9, - height: _responsiveUtils.getSizeScreenHeight(context) * 0.9, - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(16)), - child: _buildBodyDesktop(context) - ) - ) - ) + body: GestureDetector( + onTap: () => controller.clearFocusEditor(context), + child: Align(alignment: Alignment.center, child: Card( + color: Colors.transparent, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(16))), + width: _responsiveUtils.getSizeScreenWidth(context) * 0.9, + height: _responsiveUtils.getSizeScreenHeight(context) * 0.9, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: _buildBodyDesktop(context) + ) + ) + ) + ), ) ), tabletLarge: Scaffold( backgroundColor: Colors.black38, - body: Align(alignment: Alignment.center, child: Card( - color: Colors.transparent, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(16))), - width: _responsiveUtils.getSizeScreenWidth(context) * 0.6, - height: _responsiveUtils.getSizeScreenHeight(context) * 0.9, - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(16)), - child: _buildBodyDesktop(context) - ) - ) - ) + body: GestureDetector( + onTap: () => controller.clearFocusEditor(context), + child: Align(alignment: Alignment.center, child: Card( + color: Colors.transparent, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(16))), + width: _responsiveUtils.getSizeScreenWidth(context) * 0.6, + height: _responsiveUtils.getSizeScreenHeight(context) * 0.9, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: _buildBodyDesktop(context) + ) + ) + ) + ), ) ), desktop: Scaffold( backgroundColor: Colors.black38, - body: Align(alignment: Alignment.center, child: Card( - color: Colors.transparent, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(16))), - width: _responsiveUtils.getSizeScreenWidth(context) * 0.4, - height: _responsiveUtils.getSizeScreenHeight(context) * 0.9, - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(16)), - child: _buildBodyDesktop(context) - ) - ) - ) + body: GestureDetector( + onTap: () => controller.clearFocusEditor(context), + child: Align(alignment: Alignment.center, child: Card( + color: Colors.transparent, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(16))), + width: _responsiveUtils.getSizeScreenWidth(context) * 0.4, + height: _responsiveUtils.getSizeScreenHeight(context) * 0.9, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: _buildBodyDesktop(context) + ) + ) + ) + ), ) ) ); @@ -116,6 +132,7 @@ class IdentityCreatorView extends GetWidget { const SizedBox(height: 8), Expanded(child: SingleChildScrollView( physics: const ClampingScrollPhysics(), + reverse: true, child: Padding( padding: const EdgeInsets.all(24.0), child: Column(children: [ @@ -175,37 +192,9 @@ class IdentityCreatorView extends GetWidget { fontSize: 14, color: AppColor.colorContentEmail)), const Spacer(), - Obx(() => buildTextButton( - AppLocalizations.of(context).plain_text, - textStyle: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: controller.signatureType.value == SignatureType.plainText - ? AppColor.colorContentEmail - : AppColor.colorHintSearchBar), - backgroundColor: controller.signatureType.value == SignatureType.plainText - ? AppColor.emailAddressChipColor - : Colors.transparent, - width: 85, - height: 30, - radius: 10, - onTap: () => controller.selectSignatureType(SignatureType.plainText))), + Obx(() => _buildSignatureButton(context, SignatureType.plainText)), const SizedBox(width: 10), - Obx(() => buildTextButton( - AppLocalizations.of(context).html_template, - textStyle: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: controller.signatureType.value == SignatureType.htmlTemplate - ? AppColor.colorContentEmail - : AppColor.colorHintSearchBar), - backgroundColor: controller.signatureType.value == SignatureType.htmlTemplate - ? AppColor.emailAddressChipColor - : Colors.transparent, - width: 110, - height: 30, - radius: 10, - onTap: () => controller.selectSignatureType(SignatureType.htmlTemplate))), + Obx(() => _buildSignatureButton(context, SignatureType.plainText)), ]), const SizedBox(height: 8), Obx(() => Container( @@ -217,42 +206,9 @@ class IdentityCreatorView extends GetWidget { child: Stack( children: [ if (controller.signatureType.value == SignatureType.plainText) - SizedBox( - height: 230, - child: (TextFieldBuilder() - ..key(const Key('signature_plain_text_editor'),) - ..cursorColor(Colors.black) - ..addController(controller.signaturePlainEditorController) - ..textStyle(const TextStyle( - fontWeight: FontWeight.normal, - color: Colors.black, - fontSize: 16)) - ..maxLines(null)) - .build()) + _buildSignaturePlainTextTemplate(context) else - HtmlEditor( - key: const Key('signature_html_editor'), - controller: controller.signatureHtmlEditorController, - htmlEditorOptions: const HtmlEditorOptions( - hint: '', - darkMode: false, - ), - blockQuotedContent: controller.contentHtmlEditor ?? '

', - htmlToolbarOptions: const HtmlToolbarOptions( - toolbarPosition: ToolbarPosition.custom, - ), - otherOptions: const OtherOptions(height: 230), - callbacks: Callbacks(onInit: () { - controller.signatureHtmlEditorController.setFullScreen(); - }, onChangeContent: (String? changed) { - controller.updateContentHtmlEditor(changed); - }, onFocus: () { - FocusManager.instance.primaryFocus?.unfocus(); - Future.delayed(const Duration(milliseconds: 500), () { - controller.signatureHtmlEditorController.setFocus(); - }); - }), - ) + _buildSignatureHtmlTemplate(context) ] ), )) @@ -305,11 +261,14 @@ class IdentityCreatorView extends GetWidget { Column(children: [ Padding( padding: const EdgeInsets.only(top: 16), - child: Text(AppLocalizations.of(context).new_identity.inCaps, + child: Text(controller.actionType.value == IdentityActionType.create + ? AppLocalizations.of(context).new_identity.inCaps + : AppLocalizations.of(context).edit_identity.inCaps, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 21, color: Colors.black))), const SizedBox(height: 8), Expanded(child: SingleChildScrollView( physics: const ClampingScrollPhysics(), + reverse: true, child: Padding( padding: const EdgeInsets.all(24.0), child: Column(children: [ @@ -365,37 +324,9 @@ class IdentityCreatorView extends GetWidget { color: AppColor.colorContentEmail)), const SizedBox(height: 8), Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Obx(() => buildTextButton( - AppLocalizations.of(context).plain_text, - textStyle: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: controller.signatureType.value == SignatureType.plainText - ? AppColor.colorContentEmail - : AppColor.colorHintSearchBar), - backgroundColor: controller.signatureType.value == SignatureType.plainText - ? AppColor.emailAddressChipColor - : Colors.transparent, - width: 85, - height: 30, - radius: 10, - onTap: () => controller.selectSignatureType(SignatureType.plainText))), + Obx(() => _buildSignatureButton(context, SignatureType.plainText)), const SizedBox(width: 10), - Obx(() => buildTextButton( - AppLocalizations.of(context).html_template, - textStyle: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: controller.signatureType.value == SignatureType.htmlTemplate - ? AppColor.colorContentEmail - : AppColor.colorHintSearchBar), - backgroundColor: controller.signatureType.value == SignatureType.htmlTemplate - ? AppColor.emailAddressChipColor - : Colors.transparent, - width: 110, - height: 30, - radius: 10, - onTap: () => controller.selectSignatureType(SignatureType.htmlTemplate))), + Obx(() => _buildSignatureButton(context, SignatureType.htmlTemplate)), ]), const SizedBox(height: 8), Obx(() => Container( @@ -407,42 +338,9 @@ class IdentityCreatorView extends GetWidget { child: Stack( children: [ if (controller.signatureType.value == SignatureType.plainText) - SizedBox( - height: 230, - child: (TextFieldBuilder() - ..key(const Key('signature_plain_text_editor'),) - ..cursorColor(Colors.black) - ..addController(controller.signaturePlainEditorController) - ..textStyle(const TextStyle( - fontWeight: FontWeight.normal, - color: Colors.black, - fontSize: 16)) - ..maxLines(null)) - .build()) + _buildSignaturePlainTextTemplate(context) else - HtmlEditor( - key: const Key('signature_html_editor'), - controller: controller.signatureHtmlEditorController, - htmlEditorOptions: const HtmlEditorOptions( - hint: '', - darkMode: false, - ), - blockQuotedContent: controller.contentHtmlEditor ?? '

', - htmlToolbarOptions: const HtmlToolbarOptions( - toolbarPosition: ToolbarPosition.custom, - ), - otherOptions: const OtherOptions(height: 230), - callbacks: Callbacks(onInit: () { - controller.signatureHtmlEditorController.setFullScreen(); - }, onChangeContent: (String? changed) { - controller.updateContentHtmlEditor(changed); - }, onFocus: () { - FocusManager.instance.primaryFocus?.unfocus(); - Future.delayed(const Duration(milliseconds: 500), () { - controller.signatureHtmlEditorController.setFocus(); - }); - }), - ) + _buildSignatureHtmlTemplate(context) ] ), )), @@ -491,4 +389,87 @@ class IdentityCreatorView extends GetWidget { ] ); } + + Widget _buildSignatureButton(BuildContext context, SignatureType signatureType) { + return buildTextButton( + signatureType.getTitle(context), + textStyle: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: controller.signatureType.value == signatureType + ? AppColor.colorContentEmail + : AppColor.colorHintSearchBar), + backgroundColor: controller.signatureType.value == signatureType + ? AppColor.emailAddressChipColor + : Colors.transparent, + width: signatureType == SignatureType.plainText ? 85 : 110, + height: 30, + radius: 10, + onTap: () => controller.selectSignatureType(context, signatureType)); + } + + Widget _buildSignaturePlainTextTemplate(BuildContext context) { + if (BuildUtils.isWeb) { + return SizedBox( + height: 230, + child: (TextFieldBuilder() + ..key(const Key('signature_plain_text_editor')) + ..cursorColor(Colors.black) + ..addController(controller.signaturePlainEditorController) + ..textStyle(const TextStyle( + fontWeight: FontWeight.normal, + color: Colors.black, + fontSize: 16)) + ..maxLines(null)) + .build(), + ); + } else { + return(TextFieldBuilder() + ..key(const Key('signature_plain_text_editor')) + ..cursorColor(Colors.black) + ..addController(controller.signaturePlainEditorController) + ..textStyle(const TextStyle( + fontWeight: FontWeight.normal, + color: Colors.black, + fontSize: 16)) + ..minLines(12) + ..maxLines(null)) + .build(); + } + } + + Widget _buildSignatureHtmlTemplate(BuildContext context) { + if (BuildUtils.isWeb) { + return html_editor_browser.HtmlEditor( + key: const Key('signature_html_editor_web'), + controller: controller.signatureHtmlEditorController, + htmlEditorOptions: const html_editor_browser.HtmlEditorOptions( + hint: '', + darkMode: false, + ), + blockQuotedContent: controller.contentHtmlEditor ?? '

', + htmlToolbarOptions: const html_editor_browser.HtmlToolbarOptions( + toolbarPosition: html_editor_browser.ToolbarPosition.custom, + ), + otherOptions: const html_editor_browser.OtherOptions(height: 230), + callbacks: html_editor_browser.Callbacks(onInit: () { + controller.signatureHtmlEditorController.setFullScreen(); + }, onChangeContent: (String? changed) { + controller.updateContentHtmlEditor(changed); + }, onFocus: () { + FocusManager.instance.primaryFocus?.unfocus(); + Future.delayed(const Duration(milliseconds: 500), () { + controller.signatureHtmlEditorController.setFocus(); + }); + }), + ); + } else { + return html_editor_mobile.HtmlEditor( + key: const Key('signature_html_editor_mobile'), + minHeight: 230, + onCreated: (htmlEditorController) => controller.signatureHtmlEditorMobileController = htmlEditorController, + initialContent: controller.contentHtmlEditor ?? '', + ); + } + } } \ No newline at end of file diff --git a/lib/features/identity_creator/presentation/model/signature_type.dart b/lib/features/identity_creator/presentation/model/signature_type.dart index 9c88412b0..c77491f2b 100644 --- a/lib/features/identity_creator/presentation/model/signature_type.dart +++ b/lib/features/identity_creator/presentation/model/signature_type.dart @@ -1,5 +1,20 @@ +import 'package:flutter/cupertino.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + enum SignatureType { plainText, htmlTemplate +} + +extension SignatureTypeExtension on SignatureType { + + String getTitle(BuildContext context) { + switch(this) { + case SignatureType.plainText: + return AppLocalizations.of(context).plain_text; + case SignatureType.htmlTemplate: + return AppLocalizations.of(context).html_template; + } + } } \ No newline at end of file diff --git a/lib/features/identity_creator/presentation/widgets/identity_field_no_editable_builder.dart b/lib/features/identity_creator/presentation/widgets/identity_field_no_editable_builder.dart index 3c1a7f060..9bd004f05 100644 --- a/lib/features/identity_creator/presentation/widgets/identity_field_no_editable_builder.dart +++ b/lib/features/identity_creator/presentation/widgets/identity_field_no_editable_builder.dart @@ -19,7 +19,7 @@ class IdentityFieldNoEditableBuilder { const SizedBox(height: 8), Container( height: 44, - alignment: Alignment.center, + alignment: Alignment.centerLeft, width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( diff --git a/lib/features/identity_creator/presentation/widgets/identity_input_field_builder.dart b/lib/features/identity_creator/presentation/widgets/identity_input_field_builder.dart index 6e671c87e..5ba660f75 100644 --- a/lib/features/identity_creator/presentation/widgets/identity_input_field_builder.dart +++ b/lib/features/identity_creator/presentation/widgets/identity_input_field_builder.dart @@ -40,6 +40,7 @@ class IdentityInputFieldBuilder { ..onChange((value) => onChangeInputNameAction?.call(value)) ..textInputAction(TextInputAction.next) ..addController(editingController ?? TextEditingController()) + ..autoFocus(true) ..addFocusNode(focusNode) ..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) ..keyboardType(inputType ?? TextInputType.text)