Apply new style for create identity view on desktop
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -259,6 +259,7 @@ extension AppColor on Color {
|
||||
static const iconFolder = Color(0xFF297EF2);
|
||||
static const folderDivider = Color(0xFFE4E8EC);
|
||||
static const gray424244 = Color(0xFF424244);
|
||||
static const lightGrayF4F4F4 = Color(0xFFF4F4F4);
|
||||
static const redFF3347 = Color(0xFFFF3347);
|
||||
static const gray686E76 = Color(0xFF686E76);
|
||||
static const lightGrayEBEDF0 = Color(0xFFEBEDF0);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/views/checkbox/labeled_checkbox.dart';
|
||||
import 'package:core/presentation/views/semantics/checkbox_semantics.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
@@ -7,7 +8,8 @@ class CustomIconLabeledCheckbox extends LabeledCheckbox {
|
||||
|
||||
final String svgIconPath;
|
||||
final String selectedSvgIconPath;
|
||||
final FocusNode focusNode;
|
||||
final FocusNode? focusNode;
|
||||
final String? semanticsLabel;
|
||||
|
||||
const CustomIconLabeledCheckbox({
|
||||
super.key,
|
||||
@@ -15,31 +17,63 @@ class CustomIconLabeledCheckbox extends LabeledCheckbox {
|
||||
required super.onChanged,
|
||||
required this.svgIconPath,
|
||||
required this.selectedSvgIconPath,
|
||||
required this.focusNode,
|
||||
this.focusNode,
|
||||
this.semanticsLabel,
|
||||
super.value,
|
||||
super.gap = 16.0,
|
||||
super.padding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget get buildCheckboxWidget => FocusableActionDetector(
|
||||
focusNode: focusNode,
|
||||
autofocus: false,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
canRequestFocus: true,
|
||||
focusColor: AppColor.colorMailboxHovered,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
onTap: () => onChanged(!(value)),
|
||||
child: SvgPicture.asset(
|
||||
value ? selectedSvgIconPath : svgIconPath,
|
||||
width: 20,
|
||||
height: 20,
|
||||
colorFilter: AppColor.primaryColor.asFilter(),
|
||||
fit: BoxFit.fill,
|
||||
Widget get buildCheckboxWidget {
|
||||
Widget bodyWidget;
|
||||
|
||||
if (focusNode != null) {
|
||||
bodyWidget = FocusableActionDetector(
|
||||
focusNode: focusNode,
|
||||
autofocus: false,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
canRequestFocus: true,
|
||||
focusColor: AppColor.colorMailboxHovered,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
onTap: () => onChanged(!(value)),
|
||||
child: SvgPicture.asset(
|
||||
value ? selectedSvgIconPath : svgIconPath,
|
||||
width: 20,
|
||||
height: 20,
|
||||
colorFilter: AppColor.primaryColor.asFilter(),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} else {
|
||||
bodyWidget = Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
onTap: () => onChanged(!(value)),
|
||||
child: SvgPicture.asset(
|
||||
value ? selectedSvgIconPath : svgIconPath,
|
||||
width: 20,
|
||||
height: 20,
|
||||
colorFilter: AppColor.primaryColor.asFilter(),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (semanticsLabel != null) {
|
||||
return CheckboxSemantics(
|
||||
label: semanticsLabel!,
|
||||
value: value,
|
||||
child: bodyWidget,
|
||||
);
|
||||
} else {
|
||||
return bodyWidget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ abstract class InputDecorationBuilder {
|
||||
errorTextStyle = newStyle;
|
||||
}
|
||||
|
||||
void setFocusBorder(OutlineInputBorder focusBorder) {
|
||||
focusBorder = focusBorder;
|
||||
void setFocusBorder(OutlineInputBorder newFocusBorder) {
|
||||
focusBorder = newFocusBorder;
|
||||
}
|
||||
|
||||
InputDecoration build() {
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:core/utils/direction_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
|
||||
typedef SuggestionBoxDecorationBuilder = Widget Function(Widget child);
|
||||
|
||||
class TypeAheadFormFieldBuilder<T> extends StatefulWidget {
|
||||
|
||||
final TextDirection textDirection;
|
||||
@@ -11,7 +13,7 @@ class TypeAheadFormFieldBuilder<T> extends StatefulWidget {
|
||||
final SuggestionsCallback<T> suggestionsCallback;
|
||||
final ItemBuilder<T> itemBuilder;
|
||||
final SuggestionSelectionCallback<T> onSuggestionSelected;
|
||||
final Widget? suggestionsBoxDecoration;
|
||||
final SuggestionBoxDecorationBuilder? suggestionsBoxDecoration;
|
||||
final WidgetBuilder? noItemsFoundBuilder;
|
||||
final bool hideOnEmpty;
|
||||
final bool hideOnError;
|
||||
@@ -26,6 +28,7 @@ class TypeAheadFormFieldBuilder<T> extends StatefulWidget {
|
||||
final TextInputType keyboardType;
|
||||
final InputDecoration decoration;
|
||||
final Color cursorColor;
|
||||
final TextStyle? textStyle;
|
||||
|
||||
const TypeAheadFormFieldBuilder({
|
||||
super.key,
|
||||
@@ -47,6 +50,7 @@ class TypeAheadFormFieldBuilder<T> extends StatefulWidget {
|
||||
this.cursorColor = AppColor.primaryColor,
|
||||
this.autofillHints,
|
||||
this.textInputAction,
|
||||
this.textStyle,
|
||||
this.onTextChange,
|
||||
this.onTextSubmitted,
|
||||
});
|
||||
@@ -73,13 +77,14 @@ class _TypeAheadFormFieldBuilderState<T> extends State<TypeAheadFormFieldBuilder
|
||||
key: widget.key,
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
builder: (context, controller, focusNode) {
|
||||
builder: (_, controller, focusNode) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
textInputAction: widget.textInputAction,
|
||||
autocorrect: widget.autocorrect,
|
||||
autofillHints: widget.autofillHints,
|
||||
style: widget.textStyle,
|
||||
keyboardType: widget.keyboardType,
|
||||
decoration: widget.decoration,
|
||||
textDirection: _textDirection,
|
||||
@@ -103,12 +108,17 @@ class _TypeAheadFormFieldBuilderState<T> extends State<TypeAheadFormFieldBuilder
|
||||
itemBuilder: widget.itemBuilder,
|
||||
onSelected: widget.onSuggestionSelected,
|
||||
decorationBuilder: (context, child) {
|
||||
return widget.suggestionsBoxDecoration ?? Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 4,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: child,
|
||||
);
|
||||
if (widget.suggestionsBoxDecoration == null) {
|
||||
return Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 4,
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return widget.suggestionsBoxDecoration!(child);
|
||||
}
|
||||
},
|
||||
emptyBuilder: widget.noItemsFoundBuilder,
|
||||
hideOnEmpty: widget.hideOnEmpty,
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/presentation/utils/theme_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/mail/email/email_address.dart';
|
||||
import 'package:model/extensions/email_address_extension.dart';
|
||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||
|
||||
typedef OnEmailAddressSelected = void Function(EmailAddress emailAddress);
|
||||
|
||||
class DefaultEmailAddressDropDownButton extends StatelessWidget {
|
||||
final ImagePaths imagePaths;
|
||||
final List<EmailAddress> emailAddresses;
|
||||
final EmailAddress? emailAddressSelected;
|
||||
final OnEmailAddressSelected onEmailAddressSelected;
|
||||
final bool isEnabled;
|
||||
|
||||
const DefaultEmailAddressDropDownButton({
|
||||
Key? key,
|
||||
required this.imagePaths,
|
||||
required this.emailAddresses,
|
||||
required this.onEmailAddressSelected,
|
||||
this.emailAddressSelected,
|
||||
this.isEnabled = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isEnabled) {
|
||||
return DropdownButtonHideUnderline(
|
||||
child: PointerInterceptor(
|
||||
child: DropdownButton2<EmailAddress>(
|
||||
isExpanded: true,
|
||||
items: emailAddresses.map(_buildItemMenu).toList(),
|
||||
value: emailAddressSelected,
|
||||
customButton: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
border: Border.all(
|
||||
color: AppColor.m3Neutral90,
|
||||
width: 1,
|
||||
),
|
||||
color: Colors.white,
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.only(start: 12, end: 8),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
emailAddressSelected?.emailAddress ?? '',
|
||||
style: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3SurfaceBackground,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SvgPicture.asset(imagePaths.icDropDown)
|
||||
]),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
onEmailAddressSelected.call(value);
|
||||
}
|
||||
},
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: 332,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
color: Colors.white,
|
||||
border: Border.all(color: AppColor.m3Tertiary60),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Colors.black12, blurRadius: 24)
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
elevation: 0,
|
||||
offset: const Offset(0.0, -3.0),
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
radius: const Radius.circular(40),
|
||||
thickness: WidgetStateProperty.all<double>(6),
|
||||
thumbVisibility: WidgetStateProperty.all<bool>(true),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 44,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
border: Border.all(
|
||||
color: AppColor.m3Neutral90,
|
||||
width: 1,
|
||||
),
|
||||
color: AppColor.lightGrayF4F4F4,
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.only(start: 12, end: 8),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(
|
||||
emailAddressSelected?.emailAddress ?? '',
|
||||
style: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3SurfaceBackground,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DropdownMenuItem<EmailAddress> _buildItemMenu(EmailAddress emailAddress) {
|
||||
return DropdownMenuItem<EmailAddress>(
|
||||
value: emailAddress,
|
||||
enabled: emailAddress != emailAddressSelected,
|
||||
child: PointerInterceptor(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
emailAddress.emailAddress,
|
||||
style: ThemeUtils.textStyleInter400.copyWith(
|
||||
fontSize: 15,
|
||||
height: 20 / 15,
|
||||
letterSpacing: -0.15,
|
||||
color: Colors.black,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (emailAddress == emailAddressSelected)
|
||||
SvgPicture.asset(
|
||||
imagePaths.icChecked,
|
||||
width: 20,
|
||||
height: 20,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/default_field/default_label_field_widget.dart';
|
||||
|
||||
class DefaultHorizontalFieldWidget extends StatelessWidget {
|
||||
|
||||
final String label;
|
||||
final Widget child;
|
||||
final double labelMaxWidth;
|
||||
final bool useHeight;
|
||||
|
||||
const DefaultHorizontalFieldWidget({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.child,
|
||||
this.labelMaxWidth = 112,
|
||||
this.useHeight = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bodyWidget = Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: labelMaxWidth,
|
||||
child: DefaultLabelFieldWidget(label: label),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: child),
|
||||
],
|
||||
);
|
||||
|
||||
if (useHeight) {
|
||||
return SizedBox(height: 40, child: bodyWidget);
|
||||
} else {
|
||||
return bodyWidget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,16 +27,16 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin
|
||||
final ResponsiveUtils _responsiveUtils = Get.find<ResponsiveUtils>();
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final List<Widget>? extendedOption;
|
||||
final AlignmentGeometry? alignment;
|
||||
final Decoration? decoration;
|
||||
final bool isHorizontalArrange;
|
||||
|
||||
ToolbarRichTextWebBuilder({
|
||||
Key? key,
|
||||
required this.richTextWebController,
|
||||
this.padding,
|
||||
this.extendedOption,
|
||||
this.alignment,
|
||||
this.decoration,
|
||||
this.isHorizontalArrange = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -45,222 +45,255 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin
|
||||
final codeViewEnabled = richTextWebController.codeViewEnabled;
|
||||
final opacity = codeViewEnabled ? 0.5 : 1.0;
|
||||
|
||||
final listAction = [
|
||||
if (extendedOption?.isNotEmpty == true) ...extendedOption!,
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: DropDownMenuHeaderStyleWidget(
|
||||
icon: buildWrapIconStyleText(
|
||||
isSelected: richTextWebController.isMenuHeaderStyleOpen,
|
||||
icon: SvgPicture.asset(
|
||||
RichTextStyleType.headerStyle.getIcon(_imagePaths),
|
||||
colorFilter: AppColor.colorDefaultRichTextButton
|
||||
.withValues(alpha: opacity)
|
||||
.asFilter(),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
|
||||
tooltip: RichTextStyleType.headerStyle.getTooltipButton(context),
|
||||
),
|
||||
items: HeaderStyleType.values,
|
||||
onMenuStateChange: (isOpen) {
|
||||
final newStatus = isOpen
|
||||
? DropdownMenuFontStatus.open
|
||||
: DropdownMenuFontStatus.closed;
|
||||
richTextWebController.menuHeaderStyleStatus.value = newStatus;
|
||||
},
|
||||
onChanged: richTextWebController.applyHeaderStyle,
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: DropdownMenuFontSizeWidget(
|
||||
onChanged: richTextWebController.applyNewFontSize,
|
||||
selectedFontSize: richTextWebController.selectedFontSize.value,
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: SizedBox(
|
||||
width: 130,
|
||||
child: DropDownButtonWidget<FontNameType>(
|
||||
items: FontNameType.values,
|
||||
itemSelected: richTextWebController.selectedFontName.value,
|
||||
onChanged: (newFont) =>
|
||||
richTextWebController.applyNewFontStyle(newFont),
|
||||
onMenuStateChange: (isOpen) {
|
||||
final newStatus = isOpen
|
||||
? DropdownMenuFontStatus.open
|
||||
: DropdownMenuFontStatus.closed;
|
||||
richTextWebController.menuFontStatus.value = newStatus;
|
||||
},
|
||||
heightItem: 40,
|
||||
sizeIconChecked: 16,
|
||||
radiusButton: 8,
|
||||
opacity: opacity,
|
||||
dropdownWidth: 200,
|
||||
colorButton: richTextWebController.isMenuFontOpen
|
||||
? AppColor.colorBackgroundWrapIconStyleCode
|
||||
: Colors.white,
|
||||
iconArrowDown: SvgPicture.asset(_imagePaths.icStyleArrowDown),
|
||||
tooltip: RichTextStyleType.fontName.getTooltipButton(context),
|
||||
supportSelectionIcon: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildWrapIconStyleText(
|
||||
icon: buildIconWithTooltip(
|
||||
path: RichTextStyleType.textColor.getIcon(_imagePaths),
|
||||
color: richTextWebController.selectedTextColor.value,
|
||||
tooltip: RichTextStyleType.textColor.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
),
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildWrapIconStyleText(
|
||||
padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 7),
|
||||
spacing: 3,
|
||||
icon: buildIconColorBackgroundText(
|
||||
iconData: RichTextStyleType.textBackgroundColor.getIconData(),
|
||||
colorSelected:
|
||||
richTextWebController.selectedTextBackgroundColor.value,
|
||||
tooltip: RichTextStyleType.textBackgroundColor
|
||||
.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
),
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.textBackgroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
buildWrapIconStyleText(
|
||||
hasDropdown: false,
|
||||
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
|
||||
icon: Wrap(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.bold.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController
|
||||
.isTextStyleTypeSelected(RichTextStyleType.bold),
|
||||
tooltip: RichTextStyleType.bold.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.italic.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController
|
||||
.isTextStyleTypeSelected(RichTextStyleType.italic),
|
||||
tooltip: RichTextStyleType.italic.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.underline.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController
|
||||
.isTextStyleTypeSelected(RichTextStyleType.underline),
|
||||
tooltip:
|
||||
RichTextStyleType.underline.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.strikeThrough.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController
|
||||
.isTextStyleTypeSelected(RichTextStyleType.strikeThrough),
|
||||
tooltip:
|
||||
RichTextStyleType.strikeThrough.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.strikeThrough,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: PopupMenuOverlayWidget(
|
||||
controller: richTextWebController.menuParagraphController,
|
||||
listButtonAction: ParagraphType.values
|
||||
.map((paragraph) => paragraph.buildButtonWidget(
|
||||
context,
|
||||
_imagePaths,
|
||||
(paragraph) =>
|
||||
richTextWebController.applyParagraphType(paragraph)))
|
||||
.toList(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
iconButton: buildWrapIconStyleText(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
|
||||
spacing: 3,
|
||||
isSelected: richTextWebController.focusMenuParagraph.value,
|
||||
icon: buildIconWithTooltip(
|
||||
path: richTextWebController.selectedParagraph.value
|
||||
.getIcon(_imagePaths),
|
||||
color: AppColor.colorDefaultRichTextButton,
|
||||
opacity: opacity,
|
||||
tooltip: RichTextStyleType.paragraph.getTooltipButton(context),
|
||||
),
|
||||
),
|
||||
position: _responsiveUtils.isMobile(context)
|
||||
? PreferredPosition.top
|
||||
: PreferredPosition.bottom,
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: PopupMenuOverlayWidget(
|
||||
controller: richTextWebController.menuOrderListController,
|
||||
listButtonAction: OrderListType.values
|
||||
.map((orderType) => orderType.buildButtonWidget(
|
||||
context,
|
||||
_imagePaths,
|
||||
(orderType) =>
|
||||
richTextWebController.applyOrderListType(orderType)))
|
||||
.toList(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
iconButton: buildWrapIconStyleText(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
|
||||
spacing: 3,
|
||||
isSelected: richTextWebController.focusMenuOrderList.value,
|
||||
icon: buildIconWithTooltip(
|
||||
path: richTextWebController.selectedOrderList.value
|
||||
.getIcon(_imagePaths),
|
||||
color: AppColor.colorDefaultRichTextButton,
|
||||
opacity: opacity,
|
||||
tooltip: RichTextStyleType.orderList.getTooltipButton(context),
|
||||
),
|
||||
),
|
||||
position: _responsiveUtils.isMobile(context)
|
||||
? PreferredPosition.top
|
||||
: PreferredPosition.bottom,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
Widget bodyWidget;
|
||||
|
||||
if (isHorizontalArrange) {
|
||||
bodyWidget = SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: listAction,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
bodyWidget = Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
runSpacing: ToolbarRichTextBuilderStyle.itemVerticalSpace,
|
||||
spacing: ToolbarRichTextBuilderStyle.itemHorizontalSpace,
|
||||
children: listAction,
|
||||
);
|
||||
}
|
||||
|
||||
return PointerInterceptor(
|
||||
child: Container(
|
||||
padding: padding ?? ToolbarRichTextBuilderStyle.padding,
|
||||
decoration: decoration,
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
runSpacing: ToolbarRichTextBuilderStyle.itemVerticalSpace,
|
||||
spacing: ToolbarRichTextBuilderStyle.itemHorizontalSpace,
|
||||
children: [
|
||||
if (extendedOption?.isNotEmpty == true)
|
||||
...extendedOption!,
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: DropDownMenuHeaderStyleWidget(
|
||||
icon: buildWrapIconStyleText(
|
||||
isSelected: richTextWebController.isMenuHeaderStyleOpen,
|
||||
icon: SvgPicture.asset(
|
||||
RichTextStyleType.headerStyle.getIcon(_imagePaths),
|
||||
colorFilter: AppColor.colorDefaultRichTextButton.withValues(alpha: opacity).asFilter(),
|
||||
fit: BoxFit.fill
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
|
||||
tooltip: RichTextStyleType.headerStyle.getTooltipButton(context)
|
||||
),
|
||||
items: HeaderStyleType.values,
|
||||
onMenuStateChange: (isOpen) {
|
||||
final newStatus = isOpen
|
||||
? DropdownMenuFontStatus.open
|
||||
: DropdownMenuFontStatus.closed;
|
||||
richTextWebController.menuHeaderStyleStatus.value = newStatus;
|
||||
},
|
||||
onChanged: richTextWebController.applyHeaderStyle
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: DropdownMenuFontSizeWidget(
|
||||
onChanged: richTextWebController.applyNewFontSize,
|
||||
selectedFontSize: richTextWebController.selectedFontSize.value
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: SizedBox(
|
||||
width: 130,
|
||||
child: DropDownButtonWidget<FontNameType>(
|
||||
items: FontNameType.values,
|
||||
itemSelected: richTextWebController.selectedFontName.value,
|
||||
onChanged: (newFont) => richTextWebController.applyNewFontStyle(newFont),
|
||||
onMenuStateChange: (isOpen) {
|
||||
final newStatus = isOpen
|
||||
? DropdownMenuFontStatus.open
|
||||
: DropdownMenuFontStatus.closed;
|
||||
richTextWebController.menuFontStatus.value = newStatus;
|
||||
},
|
||||
heightItem: 40,
|
||||
sizeIconChecked: 16,
|
||||
radiusButton: 8,
|
||||
opacity: opacity,
|
||||
dropdownWidth: 200,
|
||||
colorButton: richTextWebController.isMenuFontOpen
|
||||
? AppColor.colorBackgroundWrapIconStyleCode
|
||||
: Colors.white,
|
||||
iconArrowDown: SvgPicture.asset(_imagePaths.icStyleArrowDown),
|
||||
tooltip: RichTextStyleType.fontName.getTooltipButton(context),
|
||||
supportSelectionIcon: true
|
||||
)
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildWrapIconStyleText(
|
||||
icon: buildIconWithTooltip(
|
||||
path: RichTextStyleType.textColor.getIcon(_imagePaths),
|
||||
color: richTextWebController.selectedTextColor.value,
|
||||
tooltip: RichTextStyleType.textColor.getTooltipButton(context),
|
||||
opacity: opacity
|
||||
),
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.textColor
|
||||
)
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildWrapIconStyleText(
|
||||
padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 7),
|
||||
spacing: 3,
|
||||
icon: buildIconColorBackgroundText(
|
||||
iconData: RichTextStyleType.textBackgroundColor.getIconData(),
|
||||
colorSelected: richTextWebController.selectedTextBackgroundColor.value,
|
||||
tooltip: RichTextStyleType.textBackgroundColor.getTooltipButton(context),
|
||||
opacity: opacity
|
||||
),
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.textBackgroundColor
|
||||
)
|
||||
),
|
||||
),
|
||||
buildWrapIconStyleText(
|
||||
hasDropdown: false,
|
||||
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
|
||||
icon: Wrap(children: [
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.bold.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.bold),
|
||||
tooltip: RichTextStyleType.bold.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.bold
|
||||
)
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.italic.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.italic),
|
||||
tooltip: RichTextStyleType.italic.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.italic
|
||||
)
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.underline.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.underline),
|
||||
tooltip: RichTextStyleType.underline.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.underline
|
||||
)
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: buildIconStyleText(
|
||||
path: RichTextStyleType.strikeThrough.getIcon(_imagePaths),
|
||||
isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.strikeThrough),
|
||||
tooltip: RichTextStyleType.strikeThrough.getTooltipButton(context),
|
||||
opacity: opacity,
|
||||
onTap: () => richTextWebController.applyRichTextStyle(
|
||||
context,
|
||||
RichTextStyleType.strikeThrough
|
||||
)
|
||||
),
|
||||
)
|
||||
])
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: PopupMenuOverlayWidget(
|
||||
controller: richTextWebController.menuParagraphController,
|
||||
listButtonAction: ParagraphType.values
|
||||
.map((paragraph) => paragraph.buildButtonWidget(
|
||||
context,
|
||||
_imagePaths,
|
||||
(paragraph) => richTextWebController.applyParagraphType(paragraph)))
|
||||
.toList(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
iconButton: buildWrapIconStyleText(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
|
||||
spacing: 3,
|
||||
isSelected: richTextWebController.focusMenuParagraph.value,
|
||||
icon: buildIconWithTooltip(
|
||||
path: richTextWebController.selectedParagraph.value.getIcon(_imagePaths),
|
||||
color: AppColor.colorDefaultRichTextButton,
|
||||
opacity: opacity,
|
||||
tooltip: RichTextStyleType.paragraph.getTooltipButton(context)
|
||||
)
|
||||
),
|
||||
position: _responsiveUtils.isMobile(context)
|
||||
? PreferredPosition.top
|
||||
: PreferredPosition.bottom,
|
||||
),
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: codeViewEnabled,
|
||||
child: PopupMenuOverlayWidget(
|
||||
controller: richTextWebController.menuOrderListController,
|
||||
listButtonAction: OrderListType.values
|
||||
.map((orderType) => orderType.buildButtonWidget(
|
||||
context,
|
||||
_imagePaths,
|
||||
(orderType) => richTextWebController.applyOrderListType(orderType)))
|
||||
.toList(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
iconButton: buildWrapIconStyleText(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
|
||||
spacing: 3,
|
||||
isSelected: richTextWebController.focusMenuOrderList.value,
|
||||
icon: buildIconWithTooltip(
|
||||
path: richTextWebController.selectedOrderList.value.getIcon(_imagePaths),
|
||||
color: AppColor.colorDefaultRichTextButton,
|
||||
opacity: opacity,
|
||||
tooltip: RichTextStyleType.orderList.getTooltipButton(context)
|
||||
)
|
||||
),
|
||||
position: _responsiveUtils.isMobile(context)
|
||||
? PreferredPosition.top
|
||||
: PreferredPosition.bottom,
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
child: bodyWidget,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,38 +1,32 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:core/presentation/constants/constants_ui.dart';
|
||||
import 'package:core/presentation/extensions/capitalize_extension.dart';
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/utils/style_utils.dart';
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:core/presentation/views/button/icon_button_web.dart';
|
||||
import 'package:core/presentation/views/responsive/responsive_widget.dart';
|
||||
import 'package:core/utils/html/html_utils.dart';
|
||||
import 'package:core/presentation/views/text/text_field_builder.dart';
|
||||
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
|
||||
import 'package:core/utils/direction_utils.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart' as html_editor_browser;
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:model/extensions/email_address_extension.dart';
|
||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||
import 'package:rich_text_composer/views/widgets/rich_text_keyboard_toolbar.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/default_field/default_email_address_drop_down_button.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/default_field/default_horizontal_field_widget.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/default_field/default_label_field_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/local_file_drop_zone_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_drop_list_field_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_field_no_editable_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_field_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_with_drop_list_field_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/insert_image_loading_indicator.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/set_default_identity_checkbox_builder.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_creator_form_desktop_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_creator_form_mobile_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_decoration_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_signature_input_field_widget.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/presentation/model/identity_action_type.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
||||
|
||||
class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
with RichTextButtonMixin {
|
||||
@@ -44,125 +38,285 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget bodyCreatorView = SingleChildScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 24),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Obx(() => IdentityInputFieldBuilder(
|
||||
AppLocalizations.of(context).nameToBeDisplayed,
|
||||
controller.errorNameIdentity.value,
|
||||
AppLocalizations.of(context).required,
|
||||
editingController: controller.inputNameIdentityController,
|
||||
focusNode: controller.inputNameIdentityFocusNode,
|
||||
isMandatory: true,
|
||||
onChangeInputNameAction: (value) => controller.updateNameIdentity(context, value)
|
||||
)),
|
||||
const SizedBox(height: 24),
|
||||
Obx(() {
|
||||
if (controller.actionType.value == IdentityActionType.create) {
|
||||
return IdentityDropListFieldBuilder(
|
||||
controller.imagePaths,
|
||||
AppLocalizations.of(context).email.inCaps,
|
||||
controller.emailOfIdentity.value,
|
||||
controller.listEmailAddressDefault,
|
||||
onSelectItemDropList: (emailAddress) => controller.updateEmailOfIdentity(context, emailAddress)
|
||||
);
|
||||
} else {
|
||||
return IdentityFieldNoEditableBuilder(
|
||||
AppLocalizations.of(context).email.inCaps,
|
||||
controller.emailOfIdentity.value
|
||||
);
|
||||
}
|
||||
}),
|
||||
const SizedBox(height: 24),
|
||||
Obx(() => IdentityDropListFieldBuilder(
|
||||
controller.imagePaths,
|
||||
AppLocalizations.of(context).reply_to,
|
||||
controller.replyToOfIdentity.value,
|
||||
controller.listEmailAddressOfReplyTo,
|
||||
onSelectItemDropList: (emailAddress) => controller.updaterReplyToOfIdentity(context, emailAddress)
|
||||
)),
|
||||
const SizedBox(height: 24),
|
||||
Obx(() => IdentityInputWithDropListFieldBuilder(
|
||||
AppLocalizations.of(context).bcc_to,
|
||||
controller.errorBccIdentity.value,
|
||||
controller.inputBccIdentityController,
|
||||
focusNode: controller.inputBccIdentityFocusNode,
|
||||
onSelectedSuggestionAction: (newEmailAddress) {
|
||||
controller.inputBccIdentityController.text = newEmailAddress?.email ?? '';
|
||||
controller.updateBccOfIdentity(newEmailAddress);
|
||||
},
|
||||
onChangeInputSuggestionAction: (pattern) {
|
||||
controller.validateInputBccAddress(context, pattern);
|
||||
if (pattern == null || pattern.trim().isEmpty) {
|
||||
controller.updateBccOfIdentity(null);
|
||||
} else {
|
||||
controller.updateBccOfIdentity(EmailAddress(null, pattern));
|
||||
}
|
||||
},
|
||||
onSuggestionCallbackAction: controller.getSuggestionEmailAddress
|
||||
)),
|
||||
const SizedBox(height: 32),
|
||||
Text(AppLocalizations.of(context).signature,
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
color: AppColor.colorContentEmail,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraintsEditor) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.colorInputBorderCreateMailbox),
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.all(16),
|
||||
child: _buildSignatureHtmlTemplate(context, constraintsEditor.maxWidth),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.draggableAppState.value == DraggableAppState.inActive) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final appLocalizations = AppLocalizations.of(context);
|
||||
Widget bodyCreatorView;
|
||||
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: LocalFileDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraintsEditor.maxWidth,
|
||||
height: constraintsEditor.maxHeight,
|
||||
margin: EdgeInsets.zero,
|
||||
onLocalFileDropZoneListener: (details) =>
|
||||
controller.onLocalFileDropZoneListener(
|
||||
context: context,
|
||||
details: details,
|
||||
maxWidth: constraintsEditor.maxWidth,
|
||||
),
|
||||
)
|
||||
if (controller.responsiveUtils.isMobile(context)) {
|
||||
bodyCreatorView = Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 17),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DefaultLabelFieldWidget(
|
||||
label: '${appLocalizations.name} (${appLocalizations.required})',
|
||||
),
|
||||
Obx(() => TextFieldBuilder(
|
||||
onTextChange: (value) => controller.updateNameIdentity(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
autoFocus: true,
|
||||
maxLines: 1,
|
||||
textDirection: DirectionUtils.getDirectionByLanguage(context),
|
||||
controller: controller.inputNameIdentityController,
|
||||
focusNode: controller.inputNameIdentityFocusNode,
|
||||
textStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3SurfaceBackground,
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
semanticLabel: 'Identity input field',
|
||||
decoration: (IdentityInputDecorationBuilder()
|
||||
..setContentPadding(const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 15,
|
||||
))
|
||||
..setErrorText(controller.errorNameIdentity.value)
|
||||
..setHintText(appLocalizations.enterName)
|
||||
..setHintStyle(ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3Tertiary,
|
||||
))
|
||||
).build(),
|
||||
)),
|
||||
const SizedBox(height: 15),
|
||||
DefaultLabelFieldWidget(label: appLocalizations.email.inCaps),
|
||||
Obx(() => DefaultEmailAddressDropDownButton(
|
||||
imagePaths: controller.imagePaths,
|
||||
emailAddresses: controller.listEmailAddressDefault,
|
||||
emailAddressSelected: controller.emailOfIdentity.value,
|
||||
isEnabled: controller.actionType.value == IdentityActionType.create,
|
||||
onEmailAddressSelected: (emailAddress) =>
|
||||
controller.updateEmailOfIdentity(
|
||||
context,
|
||||
emailAddress,
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 15),
|
||||
DefaultLabelFieldWidget(label: appLocalizations.reply_to),
|
||||
Obx(() => DefaultEmailAddressDropDownButton(
|
||||
imagePaths: controller.imagePaths,
|
||||
emailAddresses: controller.listEmailAddressOfReplyTo,
|
||||
emailAddressSelected: controller.replyToOfIdentity.value,
|
||||
onEmailAddressSelected: (emailAddress) =>
|
||||
controller.updaterReplyToOfIdentity(
|
||||
context,
|
||||
emailAddress,
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 15),
|
||||
DefaultLabelFieldWidget(label: appLocalizations.bcc_to),
|
||||
Obx(() {
|
||||
return TypeAheadFormFieldBuilder<EmailAddress>(
|
||||
focusNode: controller.inputBccIdentityFocusNode,
|
||||
controller: controller.inputBccIdentityController,
|
||||
textInputAction: TextInputAction.done,
|
||||
textStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3SurfaceBackground,
|
||||
),
|
||||
decoration: (IdentityInputDecorationBuilder()
|
||||
..setContentPadding(const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 15,
|
||||
))
|
||||
..setErrorText(controller.errorBccIdentity.value)
|
||||
..setHintText(appLocalizations.enterEmailAddress)
|
||||
..setHintStyle(ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3Tertiary,
|
||||
))
|
||||
).build(),
|
||||
debounceDuration: const Duration(milliseconds: 500),
|
||||
suggestionsCallback: (pattern) async {
|
||||
controller.validateInputBccAddress(context, pattern);
|
||||
if (pattern.trim().isEmpty) {
|
||||
controller.updateBccOfIdentity(null);
|
||||
} else {
|
||||
controller.updateBccOfIdentity(EmailAddress(null, pattern));
|
||||
}
|
||||
return controller.getSuggestionEmailAddress(pattern);
|
||||
},
|
||||
itemBuilder: (_, emailAddress) {
|
||||
return Container(
|
||||
height: 44,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(
|
||||
emailAddress.emailAddress,
|
||||
style: ThemeUtils.textStyleInter400.copyWith(
|
||||
fontSize: 15,
|
||||
height: 20 / 15,
|
||||
letterSpacing: -0.15,
|
||||
color: Colors.black,
|
||||
),
|
||||
);
|
||||
}),
|
||||
Obx(() {
|
||||
bool isInserting = controller.publicAssetController?.isUploading.isTrue == true
|
||||
|| controller.isCompressingInlineImage.isTrue;
|
||||
return InsertImageLoadingIndicator(isInserting: isInserting);
|
||||
})
|
||||
],
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
},
|
||||
onSuggestionSelected: (emailSelected) {
|
||||
controller.inputBccIdentityController.text = emailSelected.emailAddress;
|
||||
controller.updateBccOfIdentity(emailSelected);
|
||||
},
|
||||
noItemsFoundBuilder: (_) => const SizedBox.shrink(),
|
||||
hideOnEmpty: true,
|
||||
hideOnError: true,
|
||||
hideOnLoading: true,
|
||||
);
|
||||
}
|
||||
}),
|
||||
const SizedBox(height: 15),
|
||||
DefaultLabelFieldWidget(label: appLocalizations.signature),
|
||||
IdentitySignatureInputFieldWidget(controller: controller),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
bodyCreatorView = SingleChildScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 32,
|
||||
end: 32,
|
||||
bottom: 24,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (controller.isMobile(context))
|
||||
_buildActionButtonMobile(context)
|
||||
]),
|
||||
),
|
||||
);
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DefaultHorizontalFieldWidget(
|
||||
label: '${appLocalizations.name} (${appLocalizations.required})',
|
||||
child: Obx(() => TextFieldBuilder(
|
||||
onTextChange: (value) => controller.updateNameIdentity(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
autoFocus: true,
|
||||
maxLines: 1,
|
||||
textDirection: DirectionUtils.getDirectionByLanguage(context),
|
||||
controller: controller.inputNameIdentityController,
|
||||
focusNode: controller.inputNameIdentityFocusNode,
|
||||
textStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3SurfaceBackground,
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
semanticLabel: 'Identity input field',
|
||||
decoration: (IdentityInputDecorationBuilder()
|
||||
..setContentPadding(const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 15,
|
||||
))
|
||||
..setErrorText(controller.errorNameIdentity.value)
|
||||
..setHintText(appLocalizations.enterName)
|
||||
..setHintStyle(ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3Tertiary,
|
||||
))
|
||||
).build(),
|
||||
)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DefaultHorizontalFieldWidget(
|
||||
label: appLocalizations.email.inCaps,
|
||||
child: Obx(() => DefaultEmailAddressDropDownButton(
|
||||
imagePaths: controller.imagePaths,
|
||||
emailAddresses: controller.listEmailAddressDefault,
|
||||
emailAddressSelected: controller.emailOfIdentity.value,
|
||||
isEnabled: controller.actionType.value == IdentityActionType.create,
|
||||
onEmailAddressSelected: (emailAddress) =>
|
||||
controller.updateEmailOfIdentity(
|
||||
context,
|
||||
emailAddress,
|
||||
),
|
||||
)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DefaultHorizontalFieldWidget(
|
||||
label: appLocalizations.reply_to,
|
||||
child: Obx(() => DefaultEmailAddressDropDownButton(
|
||||
imagePaths: controller.imagePaths,
|
||||
emailAddresses: controller.listEmailAddressOfReplyTo,
|
||||
emailAddressSelected: controller.replyToOfIdentity.value,
|
||||
onEmailAddressSelected: (emailAddress) =>
|
||||
controller.updaterReplyToOfIdentity(
|
||||
context,
|
||||
emailAddress,
|
||||
),
|
||||
)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DefaultHorizontalFieldWidget(
|
||||
label: appLocalizations.bcc_to,
|
||||
child: Obx(() {
|
||||
return TypeAheadFormFieldBuilder<EmailAddress>(
|
||||
focusNode: controller.inputBccIdentityFocusNode,
|
||||
controller: controller.inputBccIdentityController,
|
||||
textInputAction: TextInputAction.done,
|
||||
textStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3SurfaceBackground,
|
||||
),
|
||||
decoration: (IdentityInputDecorationBuilder()
|
||||
..setContentPadding(const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 15,
|
||||
))
|
||||
..setErrorText(controller.errorBccIdentity.value)
|
||||
..setHintText(appLocalizations.enterEmailAddress)
|
||||
..setHintStyle(ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.m3Tertiary,
|
||||
))
|
||||
).build(),
|
||||
debounceDuration: const Duration(milliseconds: 500),
|
||||
suggestionsCallback: (pattern) async {
|
||||
controller.validateInputBccAddress(context, pattern);
|
||||
if (pattern.trim().isEmpty) {
|
||||
controller.updateBccOfIdentity(null);
|
||||
} else {
|
||||
controller.updateBccOfIdentity(EmailAddress(null, pattern));
|
||||
}
|
||||
return controller.getSuggestionEmailAddress(pattern);
|
||||
},
|
||||
itemBuilder: (_, emailAddress) {
|
||||
return Container(
|
||||
height: 44,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(
|
||||
emailAddress.emailAddress,
|
||||
style: ThemeUtils.textStyleInter400.copyWith(
|
||||
fontSize: 15,
|
||||
height: 20 / 15,
|
||||
letterSpacing: -0.15,
|
||||
color: Colors.black,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
},
|
||||
onSuggestionSelected: (emailSelected) {
|
||||
controller.inputBccIdentityController.text = emailSelected.emailAddress;
|
||||
controller.updateBccOfIdentity(emailSelected);
|
||||
},
|
||||
noItemsFoundBuilder: (_) => const SizedBox.shrink(),
|
||||
hideOnEmpty: true,
|
||||
hideOnError: true,
|
||||
hideOnLoading: true,
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DefaultHorizontalFieldWidget(
|
||||
label: appLocalizations.signature,
|
||||
child: IdentitySignatureInputFieldWidget(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (PlatformInfo.isWeb) {
|
||||
bodyCreatorView = NotificationListener<ScrollNotification>(
|
||||
@@ -177,103 +331,16 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
);
|
||||
|
||||
return PointerInterceptor(
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.clearFocusEditor(context),
|
||||
child: ResponsiveWidget(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
mobile: Scaffold(
|
||||
backgroundColor: Colors.black38,
|
||||
body: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
borderOnForeground: false,
|
||||
color: Colors.transparent,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(16),
|
||||
topLeft: Radius.circular(16)),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(16),
|
||||
topLeft: Radius.circular(16)
|
||||
),
|
||||
),
|
||||
child: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(child: bodyCreatorView)
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
landscapeMobile: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(child: bodyCreatorView)
|
||||
])
|
||||
),
|
||||
tablet: Scaffold(
|
||||
backgroundColor: Colors.black38,
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 24),
|
||||
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: math.max(controller.responsiveUtils.getSizeScreenWidth(context) * 0.4, 700),
|
||||
height: controller.responsiveUtils.getSizeScreenHeight(context) * 0.8,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(child: bodyCreatorView),
|
||||
const SizedBox(height: 12),
|
||||
_buildActionButtonDesktop(context)
|
||||
])
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
desktop: Scaffold(
|
||||
backgroundColor: Colors.black38,
|
||||
body: 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: math.max(controller.responsiveUtils.getSizeScreenWidth(context) * 0.4, 800),
|
||||
height: controller.responsiveUtils.getSizeScreenHeight(context) * 0.8,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(child: bodyCreatorView),
|
||||
const SizedBox(height: 12),
|
||||
_buildActionButtonDesktop(context)
|
||||
])
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
child: ResponsiveWidget(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
mobile: IdentityCreatorFormMobileBuilder(
|
||||
controller: controller,
|
||||
formView: bodyCreatorView,
|
||||
),
|
||||
tablet: IdentityCreatorFormDesktopBuilder(
|
||||
controller: controller,
|
||||
formView: bodyCreatorView,
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -319,7 +386,6 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
),
|
||||
),
|
||||
child: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(child: bodyCreatorView)
|
||||
]),
|
||||
),
|
||||
@@ -328,38 +394,6 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
),
|
||||
),
|
||||
),
|
||||
landscapeMobile: GestureDetector(
|
||||
onTap: () => controller.clearFocusEditor(context),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
left: false,
|
||||
right: false,
|
||||
child: KeyboardRichText(
|
||||
keyBroadToolbar: RichTextKeyboardToolBar(
|
||||
rootContext: context,
|
||||
titleBack: AppLocalizations.of(context).titleFormat,
|
||||
backgroundKeyboardToolBarColor: PlatformInfo.isIOS
|
||||
? AppColor.colorBackgroundKeyboard
|
||||
: AppColor.colorBackgroundKeyboardAndroid,
|
||||
richTextController: controller.richTextMobileTabletController!.richTextController,
|
||||
quickStyleLabel: AppLocalizations.of(context).titleQuickStyles,
|
||||
backgroundLabel: AppLocalizations.of(context).titleBackground,
|
||||
foregroundLabel: AppLocalizations.of(context).titleForeground,
|
||||
formatLabel: AppLocalizations.of(context).titleFormat,
|
||||
insertImage: () => controller.pickImage(context),
|
||||
),
|
||||
richTextController: controller.richTextMobileTabletController!.richTextController,
|
||||
child: SafeArea(
|
||||
child: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(child: bodyCreatorView)
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
tablet: Portal(
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.clearFocusEditor(context),
|
||||
@@ -398,10 +432,7 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
width: math.max(controller.responsiveUtils.getSizeScreenWidth(context) * 0.4, 700),
|
||||
height: controller.responsiveUtils.getSizeScreenHeight(context) * 0.8,
|
||||
child: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(child: bodyCreatorView),
|
||||
const SizedBox(height: 12),
|
||||
_buildActionButtonDesktop(context)
|
||||
])
|
||||
)
|
||||
),
|
||||
@@ -415,252 +446,4 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHeaderView(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
height: 52,
|
||||
child: Row(children: [
|
||||
const SizedBox(width: 40),
|
||||
Expanded(child: Obx(() {
|
||||
return Text(
|
||||
controller.actionType.value == IdentityActionType.create
|
||||
? AppLocalizations.of(context).createNewIdentity.inCaps
|
||||
: AppLocalizations.of(context).edit_identity.inCaps,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: CommonTextStyle.defaultTextOverFlow,
|
||||
softWrap: CommonTextStyle.defaultSoftWrap,
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
color: Colors.black
|
||||
));
|
||||
})),
|
||||
buildIconWeb(
|
||||
iconSize: 24,
|
||||
icon: SvgPicture.asset(
|
||||
controller.imagePaths.icComposerClose,
|
||||
fit: BoxFit.fill,
|
||||
colorFilter: AppColor.colorDeleteContactIcon.asFilter()
|
||||
),
|
||||
tooltip: AppLocalizations.of(context).close,
|
||||
onTap: () => controller.closeView(context)),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSignatureHtmlTemplate(
|
||||
BuildContext context,
|
||||
double maxWidth
|
||||
) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
return Column(
|
||||
children: [
|
||||
ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController!,
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
extendedOption: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 4.0),
|
||||
child: buildWrapIconStyleText(
|
||||
icon: buildIconWithTooltip(
|
||||
path: controller.imagePaths.icAddPicture,
|
||||
tooltip: AppLocalizations.of(context).insertImage
|
||||
),
|
||||
hasDropdown: false,
|
||||
onTap: () => controller.pickImage(context)
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: 300,
|
||||
),
|
||||
child: _buildHtmlEditorWeb(
|
||||
context,
|
||||
controller.contentHtmlEditor,
|
||||
maxWidth),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: controller.contentHtmlEditor);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHtmlEditorWeb(
|
||||
BuildContext context,
|
||||
String initContent,
|
||||
double maxWidth
|
||||
) {
|
||||
return html_editor_browser.HtmlEditor(
|
||||
key: const Key('identity_create_editor_web'),
|
||||
controller: controller.richTextWebController!.editorController,
|
||||
htmlEditorOptions: html_editor_browser.HtmlEditorOptions(
|
||||
shouldEnsureVisible: true,
|
||||
hint: '',
|
||||
darkMode: false,
|
||||
cacheHTMLAssetOffline: true,
|
||||
initialText: initContent.isEmpty ? null : initContent,
|
||||
disableDragAndDrop: true,
|
||||
spellCheck: true,
|
||||
customBodyCssStyle: HtmlUtils.customCssStyleHtmlEditor(direction: AppUtils.getCurrentDirection(context)),
|
||||
),
|
||||
htmlToolbarOptions: const html_editor_browser.HtmlToolbarOptions(
|
||||
toolbarType: html_editor_browser.ToolbarType.hide,
|
||||
defaultToolbarButtons: []
|
||||
),
|
||||
otherOptions: const html_editor_browser.OtherOptions(height: 200),
|
||||
callbacks: html_editor_browser.Callbacks(
|
||||
onBeforeCommand: controller.updateContentHtmlEditor,
|
||||
onChangeContent: (content) {
|
||||
controller.updateContentHtmlEditor(content);
|
||||
if (!controller.isLoadSignatureCompleted) {
|
||||
controller.onLoadSignatureCompleted(content);
|
||||
}
|
||||
},
|
||||
onInit: () {
|
||||
controller.richTextWebController?.editorController.setOnDragDropEvent();
|
||||
controller.richTextWebController?.editorController.setFullScreen();
|
||||
controller.updateContentHtmlEditor(initContent);
|
||||
},
|
||||
onFocus: () {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
controller.richTextWebController?.editorController.setFocus();
|
||||
});
|
||||
controller.richTextWebController?.closeAllMenuPopup();
|
||||
},
|
||||
onChangeSelection: controller.richTextWebController?.onEditorSettingsChange,
|
||||
onChangeCodeview: controller.updateContentHtmlEditor,
|
||||
onDragEnter: controller.handleOnDragEnterSignatureEditorWeb,
|
||||
onDragLeave: (_) {},
|
||||
onImageUpload: (listFileUpload) => controller.onPasteImageSuccess(
|
||||
context,
|
||||
listFileUpload,
|
||||
maxWidth: maxWidth),
|
||||
onImageUploadError: (listFileUpload, base64, uploadError) =>
|
||||
controller.onPasteImageFailure(
|
||||
context,
|
||||
listFileUpload,
|
||||
base64: base64,
|
||||
uploadError: uploadError
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHtmlEditor(BuildContext context, {String? initialContent}) {
|
||||
return HtmlEditor(
|
||||
key: controller.htmlKey,
|
||||
minHeight: ConstantsUI.htmlContentMinHeight.toInt(),
|
||||
maxHeight: PlatformInfo.isIOS ? ConstantsUI.composerHtmlContentMaxHeight : null,
|
||||
addDefaultSelectionMenuItems: false,
|
||||
initialContent: initialContent ?? '',
|
||||
customStyleCss: HtmlUtils.customCssStyleHtmlEditor(direction: AppUtils.getCurrentDirection(context)),
|
||||
onCreated: (editorApi) => controller.initRichTextForMobile(context, editorApi),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtonDesktop(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: _buildCheckboxIdentityDefault(context)),
|
||||
const SizedBox(width: 12),
|
||||
_buildCancelButton(context, width: 156),
|
||||
const SizedBox(width: 12),
|
||||
_buildSaveButton(context, width: 156)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtonMobile(BuildContext context) {
|
||||
return Column(children: [
|
||||
_buildCheckboxIdentityDefault(context),
|
||||
const SizedBox(height: 24),
|
||||
SafeArea(
|
||||
child: Row(children: [
|
||||
Expanded(child: _buildCancelButton(context)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _buildSaveButton(context))
|
||||
]),
|
||||
),
|
||||
if (controller.richTextMobileTabletController != null)
|
||||
ValueListenableBuilder(
|
||||
valueListenable: controller.richTextMobileTabletController!.richTextController.richTextToolbarNotifier,
|
||||
builder: (_, isShowing, __) {
|
||||
if (isShowing) {
|
||||
return const SizedBox(height: 48);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildCheckboxIdentityDefault(BuildContext context) {
|
||||
return Obx(() {
|
||||
if (controller.isDefaultIdentitySupported.isTrue) {
|
||||
return SetDefaultIdentityCheckboxBuilder(
|
||||
imagePaths: controller.imagePaths,
|
||||
isCheck: controller.isDefaultIdentity.value,
|
||||
onCheckboxChanged: controller.onCheckboxChanged);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCancelButton(BuildContext context, {double? width}) {
|
||||
return buildTextButton(
|
||||
AppLocalizations.of(context).cancel,
|
||||
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 17,
|
||||
color: AppColor.colorTextButton,
|
||||
),
|
||||
backgroundColor: AppColor.emailAddressChipColor,
|
||||
width: width ?? 128,
|
||||
height: 44,
|
||||
radius: 10,
|
||||
onTap: () => controller.closeView(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSaveButton(BuildContext context, {double? width}) {
|
||||
return Obx(() => controller.viewState.value.fold(
|
||||
(failure) => buildTextButton(
|
||||
controller.actionType.value == IdentityActionType.create
|
||||
? AppLocalizations.of(context).create
|
||||
: AppLocalizations.of(context).save,
|
||||
width: width ?? 128,
|
||||
height: 44,
|
||||
radius: 10,
|
||||
onTap: () => controller.createNewIdentity(context)),
|
||||
(success) {
|
||||
if (success is GetAllIdentitiesLoading) {
|
||||
return const Center(
|
||||
key: Key('create_loading_icon'),
|
||||
child: CircularProgressIndicator(color: AppColor.primaryColor));
|
||||
} else {
|
||||
return buildTextButton(
|
||||
controller.actionType.value == IdentityActionType.create
|
||||
? AppLocalizations.of(context).create
|
||||
: AppLocalizations.of(context).save,
|
||||
width: width ?? 128,
|
||||
height: 44,
|
||||
radius: 10,
|
||||
onTap: () => controller.createNewIdentity(context));
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/views/checkbox/custom_icon_labeled_checkbox.dart';
|
||||
import 'package:core/presentation/views/dialog/confirm_dialog_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
class IdentityCreatorFormBottomView extends StatelessWidget {
|
||||
final IdentityCreatorController controller;
|
||||
|
||||
const IdentityCreatorFormBottomView({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appLocalizations = AppLocalizations.of(context);
|
||||
|
||||
final isMobile = controller.responsiveUtils.isMobile(context);
|
||||
|
||||
final checkboxWidget = Obx(() {
|
||||
if (controller.isDefaultIdentitySupported.isTrue) {
|
||||
return Expanded(
|
||||
child: Obx(() {
|
||||
return CustomIconLabeledCheckbox(
|
||||
label: appLocalizations.setDefaultIdentity,
|
||||
svgIconPath: controller.imagePaths.icCheckboxUnselected,
|
||||
selectedSvgIconPath: controller.imagePaths.icCheckboxSelected,
|
||||
value: controller.isDefaultIdentity.value,
|
||||
semanticsLabel: 'Set default identity checkbox',
|
||||
onChanged: (_) => controller.onCheckboxChanged(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
});
|
||||
|
||||
final cancelButtonWidget = Container(
|
||||
constraints: isMobile ? null : const BoxConstraints(minWidth: 67),
|
||||
height: 48,
|
||||
child: ConfirmDialogButton(
|
||||
label: appLocalizations.cancel,
|
||||
onTapAction: () => controller.closeView(context),
|
||||
),
|
||||
);
|
||||
|
||||
final saveButtonWidget = Container(
|
||||
constraints: isMobile ? null : const BoxConstraints(minWidth: 110),
|
||||
height: 48,
|
||||
child: Obx(() {
|
||||
final viewState = controller.viewState.value;
|
||||
final actionType = controller.actionType.value;
|
||||
final isLoading = viewState.fold(
|
||||
(failure) => false,
|
||||
(success) => success is GetAllIdentitiesLoading,
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return const Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColor.primaryColor,
|
||||
strokeWidth: 2.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return ConfirmDialogButton(
|
||||
label: actionType.getPositiveButtonTitle(appLocalizations),
|
||||
backgroundColor: AppColor.primaryMain,
|
||||
textColor: Colors.white,
|
||||
onTapAction: () => controller.createNewIdentity(context),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (isMobile) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 17),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
checkboxWidget,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 44),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: cancelButtonWidget),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: saveButtonWidget),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 32,
|
||||
end: 37,
|
||||
top: 12,
|
||||
bottom: 24,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
checkboxWidget,
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(child: cancelButtonWidget),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(child: saveButtonWidget),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/default_field/default_close_button_widget.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_creator_form_bottom_view.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_creator_form_title_widget.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
class IdentityCreatorFormDesktopBuilder extends StatelessWidget {
|
||||
final IdentityCreatorController controller;
|
||||
final Widget formView;
|
||||
|
||||
const IdentityCreatorFormDesktopBuilder({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.formView,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appLocalizations = AppLocalizations.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black.withOpacity(0.2),
|
||||
body: GestureDetector(
|
||||
onTap: () => controller.clearFocusEditor(context),
|
||||
child: Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
width: min(
|
||||
controller.responsiveUtils.getSizeScreenWidth(context) - 48,
|
||||
784,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Obx(() {
|
||||
final actionType = controller.actionType.value;
|
||||
return IdentityCreatorFormTitleWidget(
|
||||
title: actionType.getTitle(appLocalizations),
|
||||
);
|
||||
}),
|
||||
Flexible(child: formView),
|
||||
const SizedBox(height: 24),
|
||||
IdentityCreatorFormBottomView(controller: controller),
|
||||
],
|
||||
),
|
||||
DefaultCloseButtonWidget(
|
||||
iconClose: controller.imagePaths.icCloseDialog,
|
||||
onTapActionCallback: () => controller.closeView(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/default_field/default_app_bar_widget.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_creator_form_bottom_view.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
class IdentityCreatorFormMobileBuilder extends StatelessWidget {
|
||||
final IdentityCreatorController controller;
|
||||
final Widget formView;
|
||||
|
||||
const IdentityCreatorFormMobileBuilder({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.formView,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appLocalizations = AppLocalizations.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: GestureDetector(
|
||||
onTap: () => controller.clearFocusEditor(context),
|
||||
child: SafeArea(
|
||||
child: ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(() {
|
||||
final actionType = controller.actionType.value;
|
||||
return DefaultAppBarWidget(
|
||||
title: actionType.getTitle(appLocalizations),
|
||||
imagePaths: controller.imagePaths,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
onBackAction: () => controller.closeView(context),
|
||||
);
|
||||
}),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(child: formView),
|
||||
IdentityCreatorFormBottomView(controller: controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IdentityCreatorFormTitleWidget extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const IdentityCreatorFormTitleWidget({super.key, required this.title});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 32,
|
||||
end: 32,
|
||||
top: 24,
|
||||
bottom: 12,
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: ThemeUtils.textStyleM3HeadlineSmall,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/presentation/utils/style_utils.dart';
|
||||
import 'package:core/presentation/utils/theme_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/mail/email/email_address.dart';
|
||||
import 'package:model/extensions/email_address_extension.dart';
|
||||
|
||||
typedef OnSelectEmailAddressDropListAction = Function(EmailAddress? emailAddress);
|
||||
|
||||
class IdentityDropListFieldBuilder extends StatelessWidget {
|
||||
|
||||
final ImagePaths _imagePaths;
|
||||
final String _label;
|
||||
final EmailAddress? _emailAddressSelected;
|
||||
final List<EmailAddress> _listEmailAddress;
|
||||
final OnSelectEmailAddressDropListAction? onSelectItemDropList;
|
||||
|
||||
const IdentityDropListFieldBuilder(
|
||||
this._imagePaths,
|
||||
this._label,
|
||||
this._emailAddressSelected,
|
||||
this._listEmailAddress, {
|
||||
super.key,
|
||||
this.onSelectItemDropList
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(
|
||||
_label,
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColor.colorContentEmail)),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<EmailAddress>(
|
||||
isExpanded: true,
|
||||
hint: Row(
|
||||
children: [
|
||||
Expanded(child: Text(
|
||||
'',
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.black,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: CommonTextStyle.defaultTextOverFlow,
|
||||
softWrap: CommonTextStyle.defaultSoftWrap,
|
||||
)),
|
||||
],
|
||||
),
|
||||
items: _listEmailAddress.map((item) => DropdownMenuItem<EmailAddress>(
|
||||
value: item,
|
||||
child: Text(
|
||||
item.emailAddress,
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black),
|
||||
maxLines: 1,
|
||||
overflow: CommonTextStyle.defaultTextOverFlow,
|
||||
softWrap: CommonTextStyle.defaultSoftWrap,
|
||||
),
|
||||
)).toList(),
|
||||
value: _emailAddressSelected,
|
||||
onChanged: onSelectItemDropList,
|
||||
buttonStyleData: ButtonStyleData(
|
||||
height: 44,
|
||||
padding: const EdgeInsetsDirectional.only(end: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: AppColor.colorInputBorderCreateMailbox, width: 0.5),
|
||||
color: AppColor.colorInputBackgroundCreateMailbox),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: 200,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.white),
|
||||
elevation: 4,
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
radius: const Radius.circular(40),
|
||||
thickness: WidgetStateProperty.all<double>(6),
|
||||
thumbVisibility: WidgetStateProperty.all<bool>(true),
|
||||
),
|
||||
),
|
||||
iconStyleData: IconStyleData(
|
||||
icon: SvgPicture.asset(_imagePaths.icDropDown),
|
||||
iconSize: 14,
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 44,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/utils/style_utils.dart';
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
|
||||
class IdentityFieldNoEditableBuilder extends StatelessWidget {
|
||||
|
||||
final String _label;
|
||||
final EmailAddress? _emailAddressSelected;
|
||||
|
||||
const IdentityFieldNoEditableBuilder(
|
||||
this._label,
|
||||
this._emailAddressSelected,
|
||||
{super.key}
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(
|
||||
_label,
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColor.colorContentEmail)),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
height: 44,
|
||||
alignment: Alignment.centerLeft,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: AppColor.colorInputBorderCreateMailbox, width: 0.5),
|
||||
color: AppColor.colorInputBackgroundCreateMailbox),
|
||||
child: Text(
|
||||
_emailAddressSelected?.email ?? '',
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColor.colorInputBorderCreateMailbox),
|
||||
maxLines: 1,
|
||||
overflow: CommonTextStyle.defaultTextOverFlow,
|
||||
softWrap: CommonTextStyle.defaultSoftWrap
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -32,6 +32,7 @@ class IdentityInputDecorationBuilder extends InputDecorationBuilder {
|
||||
filled: true,
|
||||
fillColor: errorText?.isNotEmpty == true
|
||||
? AppColor.colorInputBackgroundErrorVerifyName
|
||||
: AppColor.colorInputBackgroundCreateMailbox);
|
||||
: Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:core/presentation/views/text/text_field_builder.dart';
|
||||
import 'package:core/utils/direction_utils.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_decoration_builder.dart';
|
||||
|
||||
typedef OnChangeInputNameAction = Function(String? value);
|
||||
|
||||
class IdentityInputFieldBuilder extends StatelessWidget {
|
||||
|
||||
final String _label;
|
||||
final String? _error;
|
||||
final String? requiredIndicator;
|
||||
final TextEditingController? editingController;
|
||||
final FocusNode? focusNode;
|
||||
final TextInputType? inputType;
|
||||
final bool isMandatory;
|
||||
final OnChangeInputNameAction? onChangeInputNameAction;
|
||||
|
||||
const IdentityInputFieldBuilder(
|
||||
this._label,
|
||||
this._error,
|
||||
this.requiredIndicator, {
|
||||
super.key,
|
||||
this.isMandatory = false,
|
||||
this.editingController,
|
||||
this.focusNode,
|
||||
this.inputType,
|
||||
this.onChangeInputNameAction
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(
|
||||
isMandatory
|
||||
? '$_label ($requiredIndicator)'
|
||||
: _label, style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColor.colorContentEmail)),
|
||||
const SizedBox(height: 8),
|
||||
TextFieldBuilder(
|
||||
onTextChange: onChangeInputNameAction,
|
||||
textInputAction: TextInputAction.next,
|
||||
autoFocus: true,
|
||||
maxLines: 1,
|
||||
textDirection: DirectionUtils.getDirectionByLanguage(context),
|
||||
controller: editingController,
|
||||
focusNode: focusNode,
|
||||
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(color: Colors.black, fontSize: 16),
|
||||
keyboardType: inputType ?? TextInputType.text,
|
||||
semanticLabel: 'Identity input field',
|
||||
decoration: (IdentityInputDecorationBuilder()
|
||||
..setContentPadding(EdgeInsets.symmetric(
|
||||
vertical: PlatformInfo.isWeb ? 16 : 12,
|
||||
horizontal: 12))
|
||||
..setErrorText(_error))
|
||||
.build(),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_decoration_builder.dart';
|
||||
|
||||
typedef OnSelectedSuggestionAction = Function(EmailAddress? emailAddress);
|
||||
typedef OnSuggestionCallbackAction = Function(String? pattern);
|
||||
typedef OnChangeInputSuggestionAction = Function(String? pattern);
|
||||
|
||||
class IdentityInputWithDropListFieldBuilder extends StatelessWidget {
|
||||
|
||||
final String _label;
|
||||
final String? _error;
|
||||
final TextEditingController? editingController;
|
||||
final FocusNode? focusNode;
|
||||
final OnSelectedSuggestionAction? onSelectedSuggestionAction;
|
||||
final OnSuggestionCallbackAction? onSuggestionCallbackAction;
|
||||
final OnChangeInputSuggestionAction? onChangeInputSuggestionAction;
|
||||
|
||||
const IdentityInputWithDropListFieldBuilder(
|
||||
this._label,
|
||||
this._error,
|
||||
this.editingController, {
|
||||
super.key,
|
||||
this.focusNode,
|
||||
this.onSelectedSuggestionAction,
|
||||
this.onSuggestionCallbackAction,
|
||||
this.onChangeInputSuggestionAction,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(
|
||||
_label,
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColor.colorContentEmail)),
|
||||
const SizedBox(height: 8),
|
||||
TypeAheadFormFieldBuilder<EmailAddress>(
|
||||
focusNode: focusNode,
|
||||
controller: editingController,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: (IdentityInputDecorationBuilder()
|
||||
..setContentPadding(EdgeInsets.symmetric(
|
||||
vertical: PlatformInfo.isWeb ? 16 : 12,
|
||||
horizontal: 12))
|
||||
..setErrorText(_error))
|
||||
.build(),
|
||||
debounceDuration: const Duration(milliseconds: 500),
|
||||
suggestionsCallback: (pattern) async {
|
||||
if (onChangeInputSuggestionAction != null) {
|
||||
onChangeInputSuggestionAction!(pattern);
|
||||
}
|
||||
if (onSuggestionCallbackAction != null) {
|
||||
return onSuggestionCallbackAction!(pattern);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context, emailAddress) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Text(
|
||||
emailAddress.email ?? '',
|
||||
style: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.black)));
|
||||
},
|
||||
onSuggestionSelected: (emailSelected) {
|
||||
if (onSelectedSuggestionAction != null) {
|
||||
onSelectedSuggestionAction!(emailSelected);
|
||||
}
|
||||
},
|
||||
noItemsFoundBuilder: (context) => const SizedBox(),
|
||||
hideOnEmpty: true,
|
||||
hideOnError: true,
|
||||
hideOnLoading: true,
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
import 'package:core/presentation/constants/constants_ui.dart';
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/utils/html/html_utils.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart' as html_editor_browser;
|
||||
import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/local_file_drop_zone_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
|
||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/insert_image_loading_indicator.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
||||
|
||||
class IdentitySignatureInputFieldWidget extends StatelessWidget
|
||||
with RichTextButtonMixin {
|
||||
final IdentityCreatorController controller;
|
||||
|
||||
IdentitySignatureInputFieldWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
border: Border.all(color: AppColor.m3Neutral90),
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 8,
|
||||
end: 8,
|
||||
top: 4,
|
||||
bottom: 8,
|
||||
),
|
||||
child: _buildSignatureHtmlTemplate(
|
||||
context,
|
||||
constraints.maxWidth,
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
final draggableAppState = controller.draggableAppState.value;
|
||||
if (draggableAppState == DraggableAppState.inActive) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Positioned.fill(
|
||||
child: PointerInterceptor(
|
||||
child: LocalFileDropZoneWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
margin: EdgeInsets.zero,
|
||||
onLocalFileDropZoneListener: (details) =>
|
||||
controller.onLocalFileDropZoneListener(
|
||||
context: context,
|
||||
details: details,
|
||||
maxWidth: constraints.maxWidth,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Obx(() {
|
||||
final isUploading =
|
||||
controller.publicAssetController?.isUploading.isTrue ?? false;
|
||||
final isCompressingInlineImage =
|
||||
controller.isCompressingInlineImage.isTrue;
|
||||
|
||||
return InsertImageLoadingIndicator(
|
||||
isInserting: isUploading || isCompressingInlineImage,
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSignatureHtmlTemplate(BuildContext context, double maxWidth) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ToolbarRichTextWebBuilder(
|
||||
richTextWebController: controller.richTextWebController!,
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
extendedOption: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 4.0),
|
||||
child: buildWrapIconStyleText(
|
||||
icon: buildIconWithTooltip(
|
||||
path: controller.imagePaths.icAddPicture,
|
||||
tooltip: AppLocalizations.of(context).insertImage,
|
||||
),
|
||||
hasDropdown: false,
|
||||
onTap: () => controller.pickImage(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 189,
|
||||
child: _buildHtmlEditorWeb(
|
||||
context,
|
||||
controller.contentHtmlEditor,
|
||||
maxWidth,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: controller.contentHtmlEditor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHtmlEditorWeb(
|
||||
BuildContext context,
|
||||
String initContent,
|
||||
double maxWidth,
|
||||
) {
|
||||
final richTextWebController = controller.richTextWebController!;
|
||||
|
||||
return html_editor_browser.HtmlEditor(
|
||||
key: const Key('identity_create_editor_web'),
|
||||
controller: richTextWebController.editorController,
|
||||
htmlEditorOptions: html_editor_browser.HtmlEditorOptions(
|
||||
shouldEnsureVisible: true,
|
||||
hint: '',
|
||||
darkMode: false,
|
||||
cacheHTMLAssetOffline: true,
|
||||
initialText: initContent.isEmpty ? null : initContent,
|
||||
disableDragAndDrop: true,
|
||||
spellCheck: true,
|
||||
customBodyCssStyle: HtmlUtils.customCssStyleHtmlEditor(
|
||||
direction: AppUtils.getCurrentDirection(context),
|
||||
),
|
||||
),
|
||||
htmlToolbarOptions: const html_editor_browser.HtmlToolbarOptions(
|
||||
toolbarType: html_editor_browser.ToolbarType.hide,
|
||||
defaultToolbarButtons: [],
|
||||
),
|
||||
otherOptions: const html_editor_browser.OtherOptions(height: 200),
|
||||
callbacks: html_editor_browser.Callbacks(
|
||||
onBeforeCommand: controller.updateContentHtmlEditor,
|
||||
onChangeContent: (content) {
|
||||
controller.updateContentHtmlEditor(content);
|
||||
if (!controller.isLoadSignatureCompleted) {
|
||||
controller.onLoadSignatureCompleted(content);
|
||||
}
|
||||
},
|
||||
onInit: () {
|
||||
richTextWebController.editorController.setOnDragDropEvent();
|
||||
richTextWebController.editorController.setFullScreen();
|
||||
controller.updateContentHtmlEditor(initContent);
|
||||
},
|
||||
onFocus: () {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
richTextWebController.editorController.setFocus();
|
||||
});
|
||||
richTextWebController.closeAllMenuPopup();
|
||||
},
|
||||
onChangeSelection: richTextWebController.onEditorSettingsChange,
|
||||
onChangeCodeview: controller.updateContentHtmlEditor,
|
||||
onDragEnter: controller.handleOnDragEnterSignatureEditorWeb,
|
||||
onDragLeave: (_) {},
|
||||
onImageUpload: (listFileUpload) => controller.onPasteImageSuccess(
|
||||
context,
|
||||
listFileUpload,
|
||||
maxWidth: maxWidth,
|
||||
),
|
||||
onImageUploadError: (listFileUpload, base64, uploadError) =>
|
||||
controller.onPasteImageFailure(
|
||||
context,
|
||||
listFileUpload,
|
||||
base64: base64,
|
||||
uploadError: uploadError,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHtmlEditor(BuildContext context, {String? initialContent}) {
|
||||
return HtmlEditor(
|
||||
key: controller.htmlKey,
|
||||
minHeight: ConstantsUI.htmlContentMinHeight.toInt(),
|
||||
maxHeight:
|
||||
PlatformInfo.isIOS ? ConstantsUI.composerHtmlContentMaxHeight : null,
|
||||
addDefaultSelectionMenuItems: false,
|
||||
initialContent: initialContent ?? '',
|
||||
customStyleCss: HtmlUtils.customCssStyleHtmlEditor(
|
||||
direction: AppUtils.getCurrentDirection(context),
|
||||
),
|
||||
onCreated: (editorApi) => controller.initRichTextForMobile(
|
||||
context,
|
||||
editorApi,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,25 @@
|
||||
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
enum IdentityActionType {
|
||||
create,
|
||||
edit
|
||||
edit;
|
||||
|
||||
String getTitle(AppLocalizations appLocalizations) {
|
||||
switch (this) {
|
||||
case IdentityActionType.create:
|
||||
return appLocalizations.createNewIdentity;
|
||||
case IdentityActionType.edit:
|
||||
return appLocalizations.edit_identity;
|
||||
}
|
||||
}
|
||||
|
||||
String getPositiveButtonTitle(AppLocalizations appLocalizations) {
|
||||
switch (this) {
|
||||
case IdentityActionType.create:
|
||||
return appLocalizations.create;
|
||||
case IdentityActionType.edit:
|
||||
return appLocalizations.save;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4641,5 +4641,17 @@
|
||||
"placeholders": {
|
||||
"period": {}
|
||||
}
|
||||
},
|
||||
"enterName": "Enter name",
|
||||
"@enterName": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"enterEmailAddress": "Enter email address",
|
||||
"@enterEmailAddress": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
@@ -4891,4 +4891,18 @@ class AppLocalizations {
|
||||
args: [period],
|
||||
);
|
||||
}
|
||||
|
||||
String get enterName {
|
||||
return Intl.message(
|
||||
'Enter name',
|
||||
name: 'enterName',
|
||||
);
|
||||
}
|
||||
|
||||
String get enterEmailAddress {
|
||||
return Intl.message(
|
||||
'Enter email address',
|
||||
name: 'enterEmailAddress',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user