TF-1486 Add insert image in signature on mobile/tablet
(cherry picked from commit 3411ecf28c01651a3747e67dd797885a10b532fe)
This commit is contained in:
@@ -147,6 +147,7 @@ extension AppColor on Color {
|
|||||||
static const colorCloseButton = Color(0xFF818C99);
|
static const colorCloseButton = Color(0xFF818C99);
|
||||||
static const colorDropShadow = Color(0x0F000000);
|
static const colorDropShadow = Color(0x0F000000);
|
||||||
static const colorBackgroundKeyboard = Color(0xFFD2D5DC);
|
static const colorBackgroundKeyboard = Color(0xFFD2D5DC);
|
||||||
|
static const colorBackgroundKeyboardAndroid = Color(0xFFF2F0F4);
|
||||||
static const colorShadowLayerBottom = Color(0x29000000);
|
static const colorShadowLayerBottom = Color(0x29000000);
|
||||||
static const colorShadowLayerTop = Color(0x1F000000);
|
static const colorShadowLayerTop = Color(0x1F000000);
|
||||||
static const colorDividerHorizontal = Color(0x1F000000);
|
static const colorDividerHorizontal = Color(0x1F000000);
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class _TypeAheadFormFieldBuilderState<T> extends State<TypeAheadFormFieldBuilder
|
|||||||
autofillHints: widget.autofillHints,
|
autofillHints: widget.autofillHints,
|
||||||
keyboardType: widget.keyboardType,
|
keyboardType: widget.keyboardType,
|
||||||
decoration: widget.decoration,
|
decoration: widget.decoration,
|
||||||
|
focusNode: widget.focusNode,
|
||||||
textDirection: _textDirection,
|
textDirection: _textDirection,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
widget.onTextChange?.call(value);
|
widget.onTextChange?.call(value);
|
||||||
|
|||||||
+13
-1
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/controller/base_rich_text_controller.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/controller/base_rich_text_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart';
|
||||||
@@ -23,4 +23,16 @@ class RichTextMobileTabletController extends BaseRichTextController {
|
|||||||
final styleSelected = newStyle ?? HeaderStyleType.normal;
|
final styleSelected = newStyle ?? HeaderStyleType.normal;
|
||||||
htmlEditorApi?.formatHeader(styleSelected.styleValue);
|
htmlEditorApi?.formatHeader(styleSelected.styleValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void insertImageAsBase64({required PlatformFile platformFile, int? maxWidth}) async {
|
||||||
|
if (platformFile.bytes != null) {
|
||||||
|
await htmlEditorApi?.insertImageData(
|
||||||
|
platformFile.bytes!,
|
||||||
|
'image/${platformFile.extension}',
|
||||||
|
maxWidth: maxWidth
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logError("RichTextWebController::insertImageAsBase64: bytes is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
extension SizeExtension on int {
|
extension SizeExtension on int {
|
||||||
|
|
||||||
int get kiloByteToBytes => this * 1024;
|
int get toBytes => this * 1024;
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@ import 'package:model/extensions/identity_extension.dart';
|
|||||||
import 'package:model/user/user_profile.dart';
|
import 'package:model/user/user_profile.dart';
|
||||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/identity_creator/presentation/extesions/size_extension.dart';
|
import 'package:tmail_ui_user/features/identity_creator/presentation/extesions/size_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/identity_creator/presentation/model/identity_creator_arguments.dart';
|
import 'package:tmail_ui_user/features/identity_creator/presentation/model/identity_creator_arguments.dart';
|
||||||
@@ -61,8 +62,10 @@ class IdentityCreatorController extends BaseController {
|
|||||||
final actionType = IdentityActionType.create.obs;
|
final actionType = IdentityActionType.create.obs;
|
||||||
final isDefaultIdentity = RxBool(false);
|
final isDefaultIdentity = RxBool(false);
|
||||||
final isDefaultIdentitySupported = RxBool(false);
|
final isDefaultIdentitySupported = RxBool(false);
|
||||||
|
final isMobileEditorFocus = RxBool(false);
|
||||||
|
|
||||||
final RichTextController keyboardRichTextController = RichTextController();
|
final RichTextController keyboardRichTextController = RichTextController();
|
||||||
|
final RichTextMobileTabletController richTextMobileTabletController = RichTextMobileTabletController();
|
||||||
final RichTextWebController richTextWebController = RichTextWebController();
|
final RichTextWebController richTextWebController = RichTextWebController();
|
||||||
final TextEditingController inputNameIdentityController = TextEditingController();
|
final TextEditingController inputNameIdentityController = TextEditingController();
|
||||||
final TextEditingController inputBccIdentityController = TextEditingController();
|
final TextEditingController inputBccIdentityController = TextEditingController();
|
||||||
@@ -79,7 +82,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
IdentityCreatorArguments? arguments;
|
IdentityCreatorArguments? arguments;
|
||||||
|
|
||||||
final GlobalKey htmlKey = GlobalKey();
|
final GlobalKey htmlKey = GlobalKey();
|
||||||
final htmlEditorMinHeight = 160;
|
final htmlEditorMinHeight = 150;
|
||||||
|
|
||||||
void updateNameIdentity(BuildContext context, String? value) {
|
void updateNameIdentity(BuildContext context, String? value) {
|
||||||
_nameIdentity = value;
|
_nameIdentity = value;
|
||||||
@@ -428,17 +431,23 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void initRichTextForMobile(BuildContext context, HtmlEditorApi editorApi) {
|
void initRichTextForMobile(BuildContext context, HtmlEditorApi editorApi) {
|
||||||
|
richTextMobileTabletController.htmlEditorApi = editorApi;
|
||||||
keyboardRichTextController.onCreateHTMLEditor(
|
keyboardRichTextController.onCreateHTMLEditor(
|
||||||
editorApi,
|
editorApi,
|
||||||
onEnterKeyDown: _onEnterKeyDownOnMobile,
|
onEnterKeyDown: _onEnterKeyDownOnMobile,
|
||||||
onFocus: _onFocusHTMLEditorOnMobile,
|
onFocus: _onFocusHTMLEditorOnMobile,
|
||||||
context: context
|
context: context
|
||||||
);
|
);
|
||||||
|
keyboardRichTextController.htmlEditorApi?.onFocusOut = () {
|
||||||
|
keyboardRichTextController.hideRichTextView();
|
||||||
|
isMobileEditorFocus.value = false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFocusHTMLEditorOnMobile() async {
|
void _onFocusHTMLEditorOnMobile() async {
|
||||||
inputBccIdentityFocusNode.unfocus();
|
inputBccIdentityFocusNode.unfocus();
|
||||||
inputNameIdentityFocusNode.unfocus();
|
inputNameIdentityFocusNode.unfocus();
|
||||||
|
isMobileEditorFocus.value = true;
|
||||||
if (htmlKey.currentContext != null) {
|
if (htmlKey.currentContext != null) {
|
||||||
await Scrollable.ensureVisible(htmlKey.currentContext!);
|
await Scrollable.ensureVisible(htmlKey.currentContext!);
|
||||||
}
|
}
|
||||||
@@ -464,7 +473,9 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pickImage(BuildContext context) async {
|
void pickImage(BuildContext context, {int? maxWidth}) async {
|
||||||
|
clearFocusEditor(context);
|
||||||
|
|
||||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||||
type: FileType.image,
|
type: FileType.image,
|
||||||
withData: true
|
withData: true
|
||||||
@@ -473,7 +484,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
final platformFile = filePickerResult?.files.single;
|
final platformFile = filePickerResult?.files.single;
|
||||||
if (platformFile != null) {
|
if (platformFile != null) {
|
||||||
_insertInlineImage(context, platformFile);
|
_insertInlineImage(context, platformFile, maxWidth: maxWidth);
|
||||||
} else {
|
} else {
|
||||||
_appToast.showToastErrorMessage(
|
_appToast.showToastErrorMessage(
|
||||||
context,
|
context,
|
||||||
@@ -486,18 +497,28 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isExceedMaxSizeInlineImage(int fileSize) =>
|
bool _isExceedMaxSizeInlineImage(int fileSize) =>
|
||||||
fileSize > IdentityCreatorConstants.maxSizeIdentityInlineImage.kiloByteToBytes;
|
fileSize > IdentityCreatorConstants.maxKBSizeIdentityInlineImage.toBytes;
|
||||||
|
|
||||||
void _insertInlineImage(BuildContext context, PlatformFile platformFile) {
|
void _insertInlineImage(
|
||||||
|
BuildContext context,
|
||||||
|
PlatformFile platformFile,
|
||||||
|
{int? maxWidth}
|
||||||
|
) {
|
||||||
if (_isExceedMaxSizeInlineImage(platformFile.size)) {
|
if (_isExceedMaxSizeInlineImage(platformFile.size)) {
|
||||||
_appToast.showToastErrorMessage(
|
_appToast.showToastErrorMessage(
|
||||||
context,
|
context,
|
||||||
AppLocalizations.of(context).pleaseChooseAnImageSizeCorrectly(
|
AppLocalizations.of(context).pleaseChooseAnImageSizeCorrectly(
|
||||||
IdentityCreatorConstants.maxSizeIdentityInlineImage
|
IdentityCreatorConstants.maxKBSizeIdentityInlineImage
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
richTextWebController.insertImageAsBase64(platformFile: platformFile);
|
if (PlatformInfo.isWeb) {
|
||||||
|
richTextWebController.insertImageAsBase64(platformFile: platformFile);
|
||||||
|
} else if (PlatformInfo.isMobile) {
|
||||||
|
richTextMobileTabletController.insertImageAsBase64(platformFile: platformFile, maxWidth: maxWidth);
|
||||||
|
} else {
|
||||||
|
logError("IdentityCreatorController::_insertInlineImage: Platform not supported");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,43 +43,33 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveWidget(
|
final responsiveWidget = ResponsiveWidget(
|
||||||
responsiveUtils: _responsiveUtils,
|
responsiveUtils: _responsiveUtils,
|
||||||
mobile: Card(
|
mobile: Scaffold(
|
||||||
margin: EdgeInsets.zero,
|
backgroundColor: Colors.black38,
|
||||||
borderOnForeground: false,
|
body: GestureDetector(
|
||||||
color: Colors.transparent,
|
onTap: () => controller.clearFocusEditor(context),
|
||||||
child: SafeArea(
|
child: Card(
|
||||||
top: PlatformInfo.isMobile,
|
margin: EdgeInsets.zero,
|
||||||
bottom: false,
|
borderOnForeground: false,
|
||||||
left: false,
|
color: Colors.transparent,
|
||||||
right: false,
|
child: SafeArea(
|
||||||
child: ClipRRect(
|
top: PlatformInfo.isMobile,
|
||||||
borderRadius: const BorderRadius.only(
|
bottom: false,
|
||||||
topRight: Radius.circular(16),
|
left: false,
|
||||||
topLeft: Radius.circular(16)),
|
right: false,
|
||||||
child: GestureDetector(
|
child: ClipRRect(
|
||||||
onTap: () => controller.clearFocusEditor(context),
|
borderRadius: const BorderRadius.only(
|
||||||
child: Scaffold(
|
topRight: Radius.circular(16),
|
||||||
backgroundColor: Colors.white,
|
topLeft: Radius.circular(16)),
|
||||||
body: Container(
|
child: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(16),
|
topRight: Radius.circular(16),
|
||||||
topLeft: Radius.circular(16)),
|
topLeft: Radius.circular(16)
|
||||||
boxShadow: [
|
),
|
||||||
BoxShadow(
|
),
|
||||||
color: AppColor.colorShadowLayerBottom,
|
|
||||||
blurRadius: 96,
|
|
||||||
spreadRadius: 96,
|
|
||||||
offset: Offset.zero),
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColor.colorShadowLayerTop,
|
|
||||||
blurRadius: 2,
|
|
||||||
spreadRadius: 2,
|
|
||||||
offset: Offset.zero),
|
|
||||||
]),
|
|
||||||
child: _buildBodyView(context),
|
child: _buildBodyView(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -150,6 +140,29 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (PlatformInfo.isWeb) {
|
||||||
|
return responsiveWidget;
|
||||||
|
} else {
|
||||||
|
return KeyboardRichText(
|
||||||
|
keyBroadToolbar: RichTextKeyboardToolBar(
|
||||||
|
titleBack: AppLocalizations.of(context).titleFormat,
|
||||||
|
backgroundKeyboardToolBarColor: PlatformInfo.isIOS
|
||||||
|
? AppColor.colorBackgroundKeyboard
|
||||||
|
: AppColor.colorBackgroundKeyboardAndroid,
|
||||||
|
isLandScapeMode: _responsiveUtils.isLandscapeMobile(context),
|
||||||
|
richTextController: controller.keyboardRichTextController,
|
||||||
|
titleQuickStyleBottomSheet: AppLocalizations.of(context).titleQuickStyles,
|
||||||
|
titleBackgroundBottomSheet: AppLocalizations.of(context).titleBackground,
|
||||||
|
titleForegroundBottomSheet: AppLocalizations.of(context).titleForeground,
|
||||||
|
titleFormatBottomSheet: AppLocalizations.of(context).titleFormat,
|
||||||
|
insertImage: () => controller.pickImage(context, maxWidth: _getMaxWidth(context).toInt()),
|
||||||
|
),
|
||||||
|
richTextController: controller.keyboardRichTextController,
|
||||||
|
paddingChild: EdgeInsets.zero,
|
||||||
|
child: responsiveWidget
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBodyView(BuildContext context) {
|
Widget _buildBodyView(BuildContext context) {
|
||||||
@@ -232,7 +245,15 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
if (_isMobile(context))
|
if (_isMobile(context))
|
||||||
_buildActionButtonMobile(context)
|
_buildActionButtonMobile(context)
|
||||||
else
|
else
|
||||||
_buildActionButtonDesktop(context)
|
_buildActionButtonDesktop(context),
|
||||||
|
if (PlatformInfo.isMobile)
|
||||||
|
Obx(() {
|
||||||
|
if (controller.isMobileEditorFocus.isTrue) {
|
||||||
|
return const SizedBox(height: 48);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
})
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -241,24 +262,7 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
onTap: () => controller.clearFocusEditor(context),
|
onTap: () => controller.clearFocusEditor(context),
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
_buildHeaderView(context),
|
_buildHeaderView(context),
|
||||||
Expanded(
|
Expanded(child: PointerInterceptor(child: bodyCreatorView))
|
||||||
child: PlatformInfo.isWeb
|
|
||||||
? PointerInterceptor(child: bodyCreatorView)
|
|
||||||
: KeyboardRichText(
|
|
||||||
keyBroadToolbar: RichTextKeyboardToolBar(
|
|
||||||
titleBack: AppLocalizations.of(context).titleFormat,
|
|
||||||
backgroundKeyboardToolBarColor: AppColor.colorBackgroundKeyboard,
|
|
||||||
isLandScapeMode: _responsiveUtils.isLandscapeMobile(context),
|
|
||||||
richTextController: controller.keyboardRichTextController,
|
|
||||||
titleQuickStyleBottomSheet: AppLocalizations.of(context).titleQuickStyles,
|
|
||||||
titleBackgroundBottomSheet: AppLocalizations.of(context).titleBackground,
|
|
||||||
titleForegroundBottomSheet: AppLocalizations.of(context).titleForeground,
|
|
||||||
titleFormatBottomSheet: AppLocalizations.of(context).titleFormat,
|
|
||||||
),
|
|
||||||
richTextController: controller.keyboardRichTextController,
|
|
||||||
paddingChild: EdgeInsets.zero,
|
|
||||||
child: bodyCreatorView),
|
|
||||||
),
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -455,5 +459,17 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isMobile(BuildContext context) => _responsiveUtils.isPortraitMobile(context) || _responsiveUtils.isLandscapeMobile(context);
|
bool _isMobile(BuildContext context) =>
|
||||||
|
_responsiveUtils.isPortraitMobile(context) ||
|
||||||
|
_responsiveUtils.isLandscapeMobile(context);
|
||||||
|
|
||||||
|
double _getMaxWidth(BuildContext context) {
|
||||||
|
if (_isMobile(context)) {
|
||||||
|
return _responsiveUtils.getSizeScreenWidth(context);
|
||||||
|
} else if (_responsiveUtils.isDesktop(context)) {
|
||||||
|
return math.max(_responsiveUtils.getSizeScreenWidth(context) * 0.4, 800);
|
||||||
|
} else {
|
||||||
|
return math.max(_responsiveUtils.getSizeScreenWidth(context) * 0.4, 700);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
class IdentityCreatorConstants {
|
class IdentityCreatorConstants {
|
||||||
static const int maxSizeIdentityInlineImage = 16; // Kilobyte
|
static const int maxKBSizeIdentityInlineImage = 16; // Kilobyte
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user