TF-1486 Add insert image in signature on mobile/tablet

(cherry picked from commit 3411ecf28c01651a3747e67dd797885a10b532fe)
This commit is contained in:
dab246
2023-07-13 00:25:12 +07:00
committed by Dat Vu
parent d072f6a243
commit ce14267928
7 changed files with 113 additions and 62 deletions
@@ -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);
@@ -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
}