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 colorDropShadow = Color(0x0F000000);
|
||||
static const colorBackgroundKeyboard = Color(0xFFD2D5DC);
|
||||
static const colorBackgroundKeyboardAndroid = Color(0xFFF2F0F4);
|
||||
static const colorShadowLayerBottom = Color(0x29000000);
|
||||
static const colorShadowLayerTop = Color(0x1F000000);
|
||||
static const colorDividerHorizontal = Color(0x1F000000);
|
||||
|
||||
@@ -74,6 +74,7 @@ class _TypeAheadFormFieldBuilderState<T> extends State<TypeAheadFormFieldBuilder
|
||||
autofillHints: widget.autofillHints,
|
||||
keyboardType: widget.keyboardType,
|
||||
decoration: widget.decoration,
|
||||
focusNode: widget.focusNode,
|
||||
textDirection: _textDirection,
|
||||
onChanged: (value) {
|
||||
widget.onTextChange?.call(value);
|
||||
|
||||
+13
-1
@@ -1,5 +1,5 @@
|
||||
|
||||
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: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';
|
||||
@@ -23,4 +23,16 @@ class RichTextMobileTabletController extends BaseRichTextController {
|
||||
final styleSelected = newStyle ?? HeaderStyleType.normal;
|
||||
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 {
|
||||
|
||||
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:rich_text_composer/rich_text_composer.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/identity_creator/presentation/extesions/size_extension.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 isDefaultIdentity = RxBool(false);
|
||||
final isDefaultIdentitySupported = RxBool(false);
|
||||
final isMobileEditorFocus = RxBool(false);
|
||||
|
||||
final RichTextController keyboardRichTextController = RichTextController();
|
||||
final RichTextMobileTabletController richTextMobileTabletController = RichTextMobileTabletController();
|
||||
final RichTextWebController richTextWebController = RichTextWebController();
|
||||
final TextEditingController inputNameIdentityController = TextEditingController();
|
||||
final TextEditingController inputBccIdentityController = TextEditingController();
|
||||
@@ -79,7 +82,7 @@ class IdentityCreatorController extends BaseController {
|
||||
IdentityCreatorArguments? arguments;
|
||||
|
||||
final GlobalKey htmlKey = GlobalKey();
|
||||
final htmlEditorMinHeight = 160;
|
||||
final htmlEditorMinHeight = 150;
|
||||
|
||||
void updateNameIdentity(BuildContext context, String? value) {
|
||||
_nameIdentity = value;
|
||||
@@ -428,17 +431,23 @@ class IdentityCreatorController extends BaseController {
|
||||
}
|
||||
|
||||
void initRichTextForMobile(BuildContext context, HtmlEditorApi editorApi) {
|
||||
richTextMobileTabletController.htmlEditorApi = editorApi;
|
||||
keyboardRichTextController.onCreateHTMLEditor(
|
||||
editorApi,
|
||||
onEnterKeyDown: _onEnterKeyDownOnMobile,
|
||||
onFocus: _onFocusHTMLEditorOnMobile,
|
||||
context: context
|
||||
);
|
||||
keyboardRichTextController.htmlEditorApi?.onFocusOut = () {
|
||||
keyboardRichTextController.hideRichTextView();
|
||||
isMobileEditorFocus.value = false;
|
||||
};
|
||||
}
|
||||
|
||||
void _onFocusHTMLEditorOnMobile() async {
|
||||
inputBccIdentityFocusNode.unfocus();
|
||||
inputNameIdentityFocusNode.unfocus();
|
||||
isMobileEditorFocus.value = true;
|
||||
if (htmlKey.currentContext != null) {
|
||||
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(
|
||||
type: FileType.image,
|
||||
withData: true
|
||||
@@ -473,7 +484,7 @@ class IdentityCreatorController extends BaseController {
|
||||
if (context.mounted) {
|
||||
final platformFile = filePickerResult?.files.single;
|
||||
if (platformFile != null) {
|
||||
_insertInlineImage(context, platformFile);
|
||||
_insertInlineImage(context, platformFile, maxWidth: maxWidth);
|
||||
} else {
|
||||
_appToast.showToastErrorMessage(
|
||||
context,
|
||||
@@ -486,18 +497,28 @@ class IdentityCreatorController extends BaseController {
|
||||
}
|
||||
|
||||
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)) {
|
||||
_appToast.showToastErrorMessage(
|
||||
context,
|
||||
AppLocalizations.of(context).pleaseChooseAnImageSizeCorrectly(
|
||||
IdentityCreatorConstants.maxSizeIdentityInlineImage
|
||||
IdentityCreatorConstants.maxKBSizeIdentityInlineImage
|
||||
)
|
||||
);
|
||||
} 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
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveWidget(
|
||||
final responsiveWidget = ResponsiveWidget(
|
||||
responsiveUtils: _responsiveUtils,
|
||||
mobile: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
borderOnForeground: false,
|
||||
color: Colors.transparent,
|
||||
child: SafeArea(
|
||||
top: PlatformInfo.isMobile,
|
||||
bottom: false,
|
||||
left: false,
|
||||
right: false,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(16),
|
||||
topLeft: Radius.circular(16)),
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.clearFocusEditor(context),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Container(
|
||||
mobile: Scaffold(
|
||||
backgroundColor: Colors.black38,
|
||||
body: GestureDetector(
|
||||
onTap: () => controller.clearFocusEditor(context),
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
borderOnForeground: false,
|
||||
color: Colors.transparent,
|
||||
child: SafeArea(
|
||||
top: PlatformInfo.isMobile,
|
||||
bottom: false,
|
||||
left: false,
|
||||
right: false,
|
||||
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)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.colorShadowLayerBottom,
|
||||
blurRadius: 96,
|
||||
spreadRadius: 96,
|
||||
offset: Offset.zero),
|
||||
BoxShadow(
|
||||
color: AppColor.colorShadowLayerTop,
|
||||
blurRadius: 2,
|
||||
spreadRadius: 2,
|
||||
offset: Offset.zero),
|
||||
]),
|
||||
topLeft: Radius.circular(16)
|
||||
),
|
||||
),
|
||||
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) {
|
||||
@@ -232,7 +245,15 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
||||
if (_isMobile(context))
|
||||
_buildActionButtonMobile(context)
|
||||
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),
|
||||
child: Column(children: [
|
||||
_buildHeaderView(context),
|
||||
Expanded(
|
||||
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),
|
||||
),
|
||||
Expanded(child: PointerInterceptor(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 {
|
||||
static const int maxSizeIdentityInlineImage = 16; // Kilobyte
|
||||
static const int maxKBSizeIdentityInlineImage = 16; // Kilobyte
|
||||
}
|
||||
Reference in New Issue
Block a user