TF-3084 Handle drop image to identity editor for public asset
This commit is contained in:
@@ -6,6 +6,7 @@ export 'presentation/extensions/url_extension.dart';
|
|||||||
export 'presentation/extensions/uri_extension.dart';
|
export 'presentation/extensions/uri_extension.dart';
|
||||||
export 'presentation/extensions/capitalize_extension.dart';
|
export 'presentation/extensions/capitalize_extension.dart';
|
||||||
export 'presentation/extensions/list_extensions.dart';
|
export 'presentation/extensions/list_extensions.dart';
|
||||||
|
export 'presentation/extensions/list_nullable_extensions.dart';
|
||||||
export 'domain/extensions/datetime_extension.dart';
|
export 'domain/extensions/datetime_extension.dart';
|
||||||
export 'presentation/extensions/html_extension.dart';
|
export 'presentation/extensions/html_extension.dart';
|
||||||
export 'presentation/extensions/compare_string_extension.dart';
|
export 'presentation/extensions/compare_string_extension.dart';
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
extension ListNullableExtensions<T> on List<T>? {
|
||||||
|
bool get validateFilesTransfer => this?.any((type) => type == 'Files') ?? false;
|
||||||
|
}
|
||||||
@@ -385,7 +385,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
|||||||
_subscriptionOnDragEnter = html.window.onDragEnter.listen((event) {
|
_subscriptionOnDragEnter = html.window.onDragEnter.listen((event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (_validateFilesTransfer(event.dataTransfer.types)) {
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -393,7 +393,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
|||||||
_subscriptionOnDragOver = html.window.onDragOver.listen((event) {
|
_subscriptionOnDragOver = html.window.onDragOver.listen((event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (_validateFilesTransfer(event.dataTransfer.types)) {
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -401,7 +401,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
|||||||
_subscriptionOnDragLeave = html.window.onDragLeave.listen((event) {
|
_subscriptionOnDragLeave = html.window.onDragLeave.listen((event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (_validateFilesTransfer(event.dataTransfer.types)) {
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.inActive;
|
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.inActive;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -409,16 +409,12 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
|||||||
_subscriptionOnDrop = html.window.onDrop.listen((event) {
|
_subscriptionOnDrop = html.window.onDrop.listen((event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (_validateFilesTransfer(event.dataTransfer.types)) {
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.inActive;
|
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.inActive;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _validateFilesTransfer(List<dynamic>? types) {
|
|
||||||
return types?.any((type) => type == 'Files') ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _saveComposerCacheOnWebAction() async {
|
Future<void> _saveComposerCacheOnWebAction() async {
|
||||||
_autoCreateEmailTag();
|
_autoCreateEmailTag();
|
||||||
|
|
||||||
@@ -2130,7 +2126,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleOnDragEnterHtmlEditorWeb(List<dynamic>? types) {
|
void handleOnDragEnterHtmlEditorWeb(List<dynamic>? types) {
|
||||||
if (_validateFilesTransfer(types)) {
|
if (types.validateFilesTransfer) {
|
||||||
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class LocalFileDropZoneWidget extends StatelessWidget {
|
|||||||
final ImagePaths imagePaths;
|
final ImagePaths imagePaths;
|
||||||
final double? width;
|
final double? width;
|
||||||
final double? height;
|
final double? height;
|
||||||
|
final EdgeInsetsGeometry margin;
|
||||||
final OnLocalFileDropZoneListener? onLocalFileDropZoneListener;
|
final OnLocalFileDropZoneListener? onLocalFileDropZoneListener;
|
||||||
|
|
||||||
const LocalFileDropZoneWidget({
|
const LocalFileDropZoneWidget({
|
||||||
@@ -21,6 +22,7 @@ class LocalFileDropZoneWidget extends StatelessWidget {
|
|||||||
required this.imagePaths,
|
required this.imagePaths,
|
||||||
this.width,
|
this.width,
|
||||||
this.height,
|
this.height,
|
||||||
|
this.margin = DropZoneWidgetStyle.margin,
|
||||||
this.onLocalFileDropZoneListener
|
this.onLocalFileDropZoneListener
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ class LocalFileDropZoneWidget extends StatelessWidget {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: DropZoneWidgetStyle.margin,
|
padding: margin,
|
||||||
child: DottedBorder(
|
child: DottedBorder(
|
||||||
borderType: BorderType.RRect,
|
borderType: BorderType.RRect,
|
||||||
radius: const Radius.circular(DropZoneWidgetStyle.radius),
|
radius: const Radius.circular(DropZoneWidgetStyle.radius),
|
||||||
@@ -52,7 +54,7 @@ class LocalFileDropZoneWidget extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(imagePaths.icDropZoneIcon),
|
Flexible(child: SvgPicture.asset(imagePaths.icDropZoneIcon)),
|
||||||
const SizedBox(height: DropZoneWidgetStyle.space),
|
const SizedBox(height: DropZoneWidgetStyle.space),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context).dropFileHereToAttachThem,
|
AppLocalizations.of(context).dropFileHereToAttachThem,
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ import 'dart:io';
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:core/presentation/extensions/list_nullable_extensions.dart';
|
||||||
import 'package:core/presentation/state/failure.dart';
|
import 'package:core/presentation/state/failure.dart';
|
||||||
import 'package:core/presentation/state/success.dart';
|
import 'package:core/presentation/state/success.dart';
|
||||||
import 'package:core/presentation/utils/keyboard_utils.dart';
|
import 'package:core/presentation/utils/keyboard_utils.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:core/utils/file_utils.dart';
|
import 'package:core/utils/file_utils.dart';
|
||||||
import 'package:core/utils/platform_info.dart';
|
import 'package:core/utils/platform_info.dart';
|
||||||
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
@@ -32,7 +35,7 @@ import 'package:rich_text_composer/views/commons/constants.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_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/composer/presentation/mixin/drag_drog_file_mixin.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';
|
||||||
import 'package:tmail_ui_user/features/identity_creator/presentation/utils/identity_creator_constants.dart';
|
import 'package:tmail_ui_user/features/identity_creator/presentation/utils/identity_creator_constants.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/email_address_validator.dart';
|
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/email_address_validator.dart';
|
||||||
@@ -40,6 +43,7 @@ import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification
|
|||||||
import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart';
|
import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
|
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart';
|
import 'package:tmail_ui_user/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart';
|
||||||
import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_identity_request.dart';
|
import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_identity_request.dart';
|
||||||
import 'package:tmail_ui_user/features/manage_account/domain/model/edit_identity_request.dart';
|
import 'package:tmail_ui_user/features/manage_account/domain/model/edit_identity_request.dart';
|
||||||
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
|
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
|
||||||
@@ -51,12 +55,16 @@ import 'package:tmail_ui_user/features/public_asset/domain/model/public_assets_i
|
|||||||
import 'package:tmail_ui_user/features/public_asset/presentation/model/public_asset_arguments.dart';
|
import 'package:tmail_ui_user/features/public_asset/presentation/model/public_asset_arguments.dart';
|
||||||
import 'package:tmail_ui_user/features/public_asset/presentation/public_asset_bindings.dart';
|
import 'package:tmail_ui_user/features/public_asset/presentation/public_asset_bindings.dart';
|
||||||
import 'package:tmail_ui_user/features/public_asset/presentation/public_asset_controller.dart';
|
import 'package:tmail_ui_user/features/public_asset/presentation/public_asset_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/upload/domain/extensions/file_info_extension.dart';
|
||||||
|
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
|
||||||
|
import 'package:tmail_ui_user/features/upload/domain/extensions/list_platform_file_extensions.dart';
|
||||||
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
|
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
|
||||||
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
|
import 'package:tmail_ui_user/main/universal_import/html_stub.dart' as html hide File;
|
||||||
|
|
||||||
class IdentityCreatorController extends BaseController {
|
class IdentityCreatorController extends BaseController with DragDropFileMixin {
|
||||||
|
|
||||||
final VerifyNameInteractor _verifyNameInteractor;
|
final VerifyNameInteractor _verifyNameInteractor;
|
||||||
final GetAllIdentitiesInteractor _getAllIdentitiesInteractor;
|
final GetAllIdentitiesInteractor _getAllIdentitiesInteractor;
|
||||||
@@ -74,6 +82,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
final isDefaultIdentity = RxBool(false);
|
final isDefaultIdentity = RxBool(false);
|
||||||
final isDefaultIdentitySupported = RxBool(false);
|
final isDefaultIdentitySupported = RxBool(false);
|
||||||
final isCompressingInlineImage = RxBool(false);
|
final isCompressingInlineImage = RxBool(false);
|
||||||
|
final draggableAppState = DraggableAppState.inActive.obs;
|
||||||
|
|
||||||
final TextEditingController inputNameIdentityController = TextEditingController();
|
final TextEditingController inputNameIdentityController = TextEditingController();
|
||||||
final TextEditingController inputBccIdentityController = TextEditingController();
|
final TextEditingController inputBccIdentityController = TextEditingController();
|
||||||
@@ -83,6 +92,10 @@ class IdentityCreatorController extends BaseController {
|
|||||||
|
|
||||||
RichTextMobileTabletController? richTextMobileTabletController;
|
RichTextMobileTabletController? richTextMobileTabletController;
|
||||||
RichTextWebController? richTextWebController;
|
RichTextWebController? richTextWebController;
|
||||||
|
StreamSubscription<html.Event>? _subscriptionOnDragEnter;
|
||||||
|
StreamSubscription<html.Event>? _subscriptionOnDragOver;
|
||||||
|
StreamSubscription<html.Event>? _subscriptionOnDragLeave;
|
||||||
|
StreamSubscription<html.Event>? _subscriptionOnDrop;
|
||||||
|
|
||||||
String? _nameIdentity;
|
String? _nameIdentity;
|
||||||
String? _contentHtmlEditor;
|
String? _contentHtmlEditor;
|
||||||
@@ -103,14 +116,23 @@ class IdentityCreatorController extends BaseController {
|
|||||||
|
|
||||||
void updateContentHtmlEditor(String? text) => _contentHtmlEditor = text;
|
void updateContentHtmlEditor(String? text) => _contentHtmlEditor = text;
|
||||||
|
|
||||||
String? get contentHtmlEditor {
|
String get contentHtmlEditor {
|
||||||
if (_contentHtmlEditor != null) {
|
if (_contentHtmlEditor != null) {
|
||||||
return _contentHtmlEditor;
|
return _contentHtmlEditor ?? '';
|
||||||
} else {
|
} else {
|
||||||
return arguments?.identity?.signatureAsString;
|
return arguments?.identity?.signatureAsString ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get maxSizeUploadByBytes {
|
||||||
|
if (session == null || accountId == null) return 0;
|
||||||
|
|
||||||
|
return session!.getCapabilityProperties<CoreCapability>(
|
||||||
|
accountId!,
|
||||||
|
CapabilityIdentifier.jmapCore
|
||||||
|
)?.maxSizeUpload?.value.toInt() ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
IdentityCreatorController(
|
IdentityCreatorController(
|
||||||
this._verifyNameInteractor,
|
this._verifyNameInteractor,
|
||||||
this._getAllIdentitiesInteractor,
|
this._getAllIdentitiesInteractor,
|
||||||
@@ -138,10 +160,14 @@ class IdentityCreatorController extends BaseController {
|
|||||||
session = arguments!.session;
|
session = arguments!.session;
|
||||||
identity = arguments!.identity;
|
identity = arguments!.identity;
|
||||||
actionType.value = arguments!.actionType;
|
actionType.value = arguments!.actionType;
|
||||||
|
if (actionType.value == IdentityActionType.create) {
|
||||||
|
isLoadSignatureCompleted = true;
|
||||||
|
}
|
||||||
_checkDefaultIdentityIsSupported();
|
_checkDefaultIdentityIsSupported();
|
||||||
_checkPublicAssetCapability();
|
_checkPublicAssetCapability();
|
||||||
_setUpValueFromIdentity();
|
_setUpValueFromIdentity();
|
||||||
_getAllIdentities();
|
_getAllIdentities();
|
||||||
|
_triggerBrowserEventListener();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +178,46 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _triggerBrowserEventListener() {
|
||||||
|
_subscriptionOnDragEnter = html.window.onDragEnter.listen((event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
|
draggableAppState.value = DraggableAppState.active;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_subscriptionOnDragOver = html.window.onDragOver.listen((event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
|
draggableAppState.value = DraggableAppState.active;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_subscriptionOnDragLeave = html.window.onDragLeave.listen((event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
|
draggableAppState.value = DraggableAppState.inActive;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_subscriptionOnDrop = html.window.onDrop.listen((event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.dataTransfer.types.validateFilesTransfer) {
|
||||||
|
draggableAppState.value = DraggableAppState.inActive;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleOnDragEnterSignatureEditorWeb(List<dynamic>? types) {
|
||||||
|
if (types.validateFilesTransfer) {
|
||||||
|
draggableAppState.value = DraggableAppState.active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
log('IdentityCreatorController::onClose():');
|
log('IdentityCreatorController::onClose():');
|
||||||
@@ -170,6 +236,10 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
Get.delete<PublicAssetController>(tag: BindingTag.publicAssetBindingsTag);
|
Get.delete<PublicAssetController>(tag: BindingTag.publicAssetBindingsTag);
|
||||||
publicAssetController = null;
|
publicAssetController = null;
|
||||||
|
_subscriptionOnDragEnter?.cancel();
|
||||||
|
_subscriptionOnDragOver?.cancel();
|
||||||
|
_subscriptionOnDragLeave?.cancel();
|
||||||
|
_subscriptionOnDrop?.cancel();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +247,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
void handleSuccessViewState(Success success) {
|
void handleSuccessViewState(Success success) {
|
||||||
super.handleSuccessViewState(success);
|
super.handleSuccessViewState(success);
|
||||||
if (success is GetAllIdentitiesSuccess) {
|
if (success is GetAllIdentitiesSuccess) {
|
||||||
_getALlIdentitiesSuccess(success);
|
_getAllIdentitiesSuccess(success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +255,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
void handleFailureViewState(Failure failure) {
|
void handleFailureViewState(Failure failure) {
|
||||||
super.handleFailureViewState(failure);
|
super.handleFailureViewState(failure);
|
||||||
if (failure is GetAllIdentitiesFailure) {
|
if (failure is GetAllIdentitiesFailure) {
|
||||||
_getALlIdentitiesFailure(failure);
|
_getAllIdentitiesFailure(failure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +288,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getALlIdentitiesSuccess(GetAllIdentitiesSuccess success) {
|
void _getAllIdentitiesSuccess(GetAllIdentitiesSuccess success) {
|
||||||
if (success.identities?.isNotEmpty == true) {
|
if (success.identities?.isNotEmpty == true) {
|
||||||
listEmailAddressDefault.value = success.identities!
|
listEmailAddressDefault.value = success.identities!
|
||||||
.map((identity) => identity.toEmailAddressNoName())
|
.map((identity) => identity.toEmailAddressNoName())
|
||||||
@@ -236,7 +306,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getALlIdentitiesFailure(GetAllIdentitiesFailure failure) {
|
void _getAllIdentitiesFailure(GetAllIdentitiesFailure failure) {
|
||||||
_setDefaultEmailAddressList();
|
_setDefaultEmailAddressList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,11 +409,11 @@ class IdentityCreatorController extends BaseController {
|
|||||||
bccOfIdentity.value = newEmailAddress;
|
bccOfIdentity.value = newEmailAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> _getSignatureHtmlText() async {
|
Future<String> _getSignatureHtmlText() async {
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
return richTextWebController?.editorController.getText();
|
return (await richTextWebController?.editorController.getText()) ?? '';
|
||||||
} else {
|
} else {
|
||||||
return richTextMobileTabletController?.richTextController.htmlEditorApi?.getText();
|
return (await richTextMobileTabletController?.richTextController.htmlEditorApi?.getText()) ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,7 +450,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
final publicAssetsInIdentityArguments = PublicAssetsInIdentityArguments(
|
final publicAssetsInIdentityArguments = PublicAssetsInIdentityArguments(
|
||||||
htmlSignature: signatureHtmlText ?? '',
|
htmlSignature: signatureHtmlText,
|
||||||
preExistingPublicAssetIds: List.from(publicAssetController?.preExistingPublicAssetIds ?? []),
|
preExistingPublicAssetIds: List.from(publicAssetController?.preExistingPublicAssetIds ?? []),
|
||||||
newlyPickedPublicAssetIds: List.from(publicAssetController?.newlyPickedPublicAssetIds ?? []),
|
newlyPickedPublicAssetIds: List.from(publicAssetController?.newlyPickedPublicAssetIds ?? []),
|
||||||
);
|
);
|
||||||
@@ -390,7 +460,7 @@ class IdentityCreatorController extends BaseController {
|
|||||||
email: emailOfIdentity.value?.email,
|
email: emailOfIdentity.value?.email,
|
||||||
replyTo: replyToAddress,
|
replyTo: replyToAddress,
|
||||||
bcc: bccAddress,
|
bcc: bccAddress,
|
||||||
htmlSignature: Signature(signatureHtmlText ?? ''),
|
htmlSignature: Signature(signatureHtmlText),
|
||||||
sortOrder: sortOrder);
|
sortOrder: sortOrder);
|
||||||
|
|
||||||
final generateCreateId = Id(uuid.v1());
|
final generateCreateId = Id(uuid.v1());
|
||||||
@@ -470,6 +540,8 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void clearFocusEditor(BuildContext context) {
|
void clearFocusEditor(BuildContext context) {
|
||||||
|
inputNameIdentityFocusNode.unfocus();
|
||||||
|
inputBccIdentityFocusNode.unfocus();
|
||||||
if (PlatformInfo.isMobile) {
|
if (PlatformInfo.isMobile) {
|
||||||
richTextMobileTabletController?.richTextController.htmlEditorApi?.unfocus();
|
richTextMobileTabletController?.richTextController.htmlEditorApi?.unfocus();
|
||||||
}
|
}
|
||||||
@@ -549,47 +621,31 @@ class IdentityCreatorController extends BaseController {
|
|||||||
logError("IdentityCreatorController::pickImage: context is unmounted");
|
logError("IdentityCreatorController::pickImage: context is unmounted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isExceedMaxSizeInlineImage(int fileSize) =>
|
|
||||||
fileSize > IdentityCreatorConstants.maxKBSizeIdentityInlineImage.toBytes;
|
|
||||||
|
|
||||||
bool _isExceedMaxUploadSize(int fileSize) {
|
bool _isExceedMaxUploadSize(int fileSize) {
|
||||||
final coreCapability = session?.getCapabilityProperties<CoreCapability>(
|
return fileSize > maxSizeUploadByBytes;
|
||||||
accountId!,
|
|
||||||
CapabilityIdentifier.jmapCore
|
|
||||||
);
|
|
||||||
|
|
||||||
int maxUploadSize = coreCapability?.maxSizeUpload?.value.toInt() ?? 0;
|
|
||||||
|
|
||||||
return fileSize > maxUploadSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _insertInlineImage(
|
Future<void> _insertInlineImage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
PlatformFile platformFile,
|
PlatformFile platformFile,
|
||||||
int maxWidth
|
int maxWidth,
|
||||||
|
{PlatformFile? compressedFile}
|
||||||
) async {
|
) async {
|
||||||
final PlatformFile file;
|
final PlatformFile? file;
|
||||||
try {
|
if (compressedFile != null) {
|
||||||
isCompressingInlineImage.value = true;
|
file = compressedFile;
|
||||||
file = await _compressImage(platformFile, maxWidth);
|
} else {
|
||||||
isCompressingInlineImage.value = false;
|
file = await _compressFileAction(context, originalFile: platformFile, maxWidth: maxWidth);
|
||||||
} catch (e) {
|
|
||||||
logError("IdentityCreatorController::_insertInlineImage: compress image error: $e");
|
|
||||||
isCompressingInlineImage.value = false;
|
|
||||||
if (context.mounted) {
|
|
||||||
appToast.showToastErrorMessage(
|
|
||||||
context,
|
|
||||||
AppLocalizations.of(context).cannotCompressInlineImage);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (_isExceedMaxSizeInlineImage(file.size) || _isExceedMaxUploadSize(file.size)) {
|
if (file == null) return;
|
||||||
|
|
||||||
|
if (_isExceedMaxUploadSize(file.size)) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
appToast.showToastErrorMessage(
|
appToast.showToastErrorMessage(
|
||||||
context,
|
context,
|
||||||
AppLocalizations.of(context).pleaseChooseAnImageSizeCorrectly(
|
AppLocalizations.of(context).pleaseChooseAnImageSizeCorrectly(
|
||||||
IdentityCreatorConstants.maxKBSizeIdentityInlineImage
|
maxSizeUploadByBytes
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -613,8 +669,32 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<PlatformFile?> _compressFileAction(
|
||||||
|
BuildContext context,
|
||||||
|
{
|
||||||
|
required PlatformFile originalFile,
|
||||||
|
required int maxWidth
|
||||||
|
}
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
isCompressingInlineImage.value = true;
|
||||||
|
final compressedFile = await _compressImage(originalFile, maxWidth);
|
||||||
|
isCompressingInlineImage.value = false;
|
||||||
|
return compressedFile;
|
||||||
|
} catch (e) {
|
||||||
|
logError("$runtimeType::_compressFileAction: compress image error: $e");
|
||||||
|
isCompressingInlineImage.value = false;
|
||||||
|
if (context.mounted) {
|
||||||
|
appToast.showToastErrorMessage(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context).cannotCompressInlineImage);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<PlatformFile> _compressImage(PlatformFile originalFile, int maxWidthCompressedImage) async {
|
Future<PlatformFile> _compressImage(PlatformFile originalFile, int maxWidthCompressedImage) async {
|
||||||
if (originalFile.size <= IdentityCreatorConstants.maxKBSizeIdentityInlineImage.toBytes) {
|
if (originalFile.size <= maxSizeUploadByBytes) {
|
||||||
return originalFile;
|
return originalFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,4 +781,58 @@ class IdentityCreatorController extends BaseController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onLocalFileDropZoneListener({
|
||||||
|
required BuildContext context,
|
||||||
|
required DropDoneDetails details,
|
||||||
|
required double maxWidth
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
clearFocusEditor(context);
|
||||||
|
|
||||||
|
final listFileInfo = await onDragDone(context: context, details: details);
|
||||||
|
final listImages = listFileInfo.listInlineFiles;
|
||||||
|
|
||||||
|
if (listImages.isEmpty && listFileInfo.isNotEmpty && context.mounted) {
|
||||||
|
appToast.showToastErrorMessage(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context).canNotUploadFileToSignature
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final listCompressedImages = await Future.wait(
|
||||||
|
listImages.map((fileInfo) => _compressFileAction(
|
||||||
|
context,
|
||||||
|
originalFile: fileInfo.toPlatformFile(),
|
||||||
|
maxWidth: maxWidth.toInt()))
|
||||||
|
).then((listPlatformFiles) => listPlatformFiles.whereNotNull().toList());
|
||||||
|
|
||||||
|
if (_isExceedMaxUploadSize(listCompressedImages.totalFilesSize)) {
|
||||||
|
if (context.mounted) {
|
||||||
|
appToast.showToastErrorMessage(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context).pleaseChooseAnImageSizeCorrectly(
|
||||||
|
maxSizeUploadByBytes
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logError("IdentityCreatorController::onLocalFileDropZoneListener: context is unmounted");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.forEach(
|
||||||
|
listCompressedImages,
|
||||||
|
(platformFile) => _insertInlineImage(
|
||||||
|
context,
|
||||||
|
platformFile,
|
||||||
|
maxWidth.toInt(),
|
||||||
|
compressedFile: platformFile
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logError("IdentityCreatorController::onLocalFileDropZoneListener: error: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,14 +17,16 @@ import 'package:pointer_interceptor/pointer_interceptor.dart';
|
|||||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||||
import 'package:rich_text_composer/views/widgets/rich_text_keyboard_toolbar.dart';
|
import 'package:rich_text_composer/views/widgets/rich_text_keyboard_toolbar.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.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/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/identity_creator_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/compress_image_loading_bar_widget.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_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_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_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/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/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/manage_account/domain/state/get_all_identities_state.dart';
|
||||||
import 'package:tmail_ui_user/features/manage_account/presentation/model/identity_action_type.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/localizations/app_localizations.dart';
|
||||||
@@ -40,87 +42,126 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget bodyCreatorView = SingleChildScrollView(
|
Widget bodyCreatorView = NotificationListener<ScrollNotification>(
|
||||||
controller: controller.scrollController,
|
onNotification: (scrollInfo) {
|
||||||
physics: const ClampingScrollPhysics(),
|
if (scrollInfo is ScrollEndNotification &&
|
||||||
child: Padding(
|
scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
|
||||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 24),
|
controller.clearFocusEditor(context);
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
}
|
||||||
Obx(() => IdentityInputFieldBuilder(
|
return false;
|
||||||
AppLocalizations.of(context).name,
|
},
|
||||||
controller.errorNameIdentity.value,
|
child: SingleChildScrollView(
|
||||||
AppLocalizations.of(context).required,
|
controller: controller.scrollController,
|
||||||
editingController: controller.inputNameIdentityController,
|
physics: const ClampingScrollPhysics(),
|
||||||
focusNode: controller.inputNameIdentityFocusNode,
|
child: Padding(
|
||||||
isMandatory: true,
|
padding: const EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 24),
|
||||||
onChangeInputNameAction: (value) => controller.updateNameIdentity(context, value)
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
)),
|
Obx(() => IdentityInputFieldBuilder(
|
||||||
const SizedBox(height: 24),
|
AppLocalizations.of(context).name,
|
||||||
Obx(() {
|
controller.errorNameIdentity.value,
|
||||||
if (controller.actionType.value == IdentityActionType.create) {
|
AppLocalizations.of(context).required,
|
||||||
return IdentityDropListFieldBuilder(
|
editingController: controller.inputNameIdentityController,
|
||||||
controller.imagePaths,
|
focusNode: controller.inputNameIdentityFocusNode,
|
||||||
AppLocalizations.of(context).email.inCaps,
|
isMandatory: true,
|
||||||
controller.emailOfIdentity.value,
|
onChangeInputNameAction: (value) => controller.updateNameIdentity(context, value)
|
||||||
controller.listEmailAddressDefault,
|
)),
|
||||||
onSelectItemDropList: (emailAddress) => controller.updateEmailOfIdentity(context, emailAddress)
|
const SizedBox(height: 24),
|
||||||
);
|
Obx(() {
|
||||||
} else {
|
if (controller.actionType.value == IdentityActionType.create) {
|
||||||
return IdentityFieldNoEditableBuilder(
|
return IdentityDropListFieldBuilder(
|
||||||
AppLocalizations.of(context).email.inCaps,
|
controller.imagePaths,
|
||||||
controller.emailOfIdentity.value
|
AppLocalizations.of(context).email.inCaps,
|
||||||
);
|
controller.emailOfIdentity.value,
|
||||||
}
|
controller.listEmailAddressDefault,
|
||||||
}),
|
onSelectItemDropList: (emailAddress) => controller.updateEmailOfIdentity(context, emailAddress)
|
||||||
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 {
|
} else {
|
||||||
controller.updateBccOfIdentity(EmailAddress(null, pattern));
|
return IdentityFieldNoEditableBuilder(
|
||||||
|
AppLocalizations.of(context).email.inCaps,
|
||||||
|
controller.emailOfIdentity.value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
onSuggestionCallbackAction: controller.getSuggestionEmailAddress
|
const SizedBox(height: 24),
|
||||||
)),
|
Obx(() => IdentityDropListFieldBuilder(
|
||||||
const SizedBox(height: 32),
|
controller.imagePaths,
|
||||||
Text(AppLocalizations.of(context).signature,
|
AppLocalizations.of(context).reply_to,
|
||||||
style: const TextStyle(
|
controller.replyToOfIdentity.value,
|
||||||
fontWeight: FontWeight.normal,
|
controller.listEmailAddressOfReplyTo,
|
||||||
fontSize: 14,
|
onSelectItemDropList: (emailAddress) => controller.updaterReplyToOfIdentity(context, emailAddress)
|
||||||
color: AppColor.colorContentEmail,
|
)),
|
||||||
|
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: const TextStyle(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColor.colorContentEmail,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 8),
|
LayoutBuilder(
|
||||||
Container(
|
builder: (context, constraintsEditor) {
|
||||||
decoration: BoxDecoration(
|
return Stack(
|
||||||
borderRadius: BorderRadius.circular(12),
|
children: [
|
||||||
border: Border.all(color: AppColor.colorInputBorderCreateMailbox),
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: AppColor.colorInputBorderCreateMailbox),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsetsDirectional.all(16),
|
||||||
|
child: _buildSignatureHtmlTemplate(context),
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
if (controller.draggableAppState.value == DraggableAppState.inActive) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
padding: const EdgeInsetsDirectional.all(16),
|
const SizedBox(height: 12),
|
||||||
child: _buildSignatureHtmlTemplate(context),
|
if (controller.isMobile(context))
|
||||||
),
|
_buildActionButtonMobile(context)
|
||||||
const SizedBox(height: 12),
|
]),
|
||||||
if (controller.isMobile(context))
|
),
|
||||||
_buildActionButtonMobile(context)
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -400,13 +441,9 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSignatureHtmlTemplate(BuildContext context) {
|
Widget _buildSignatureHtmlTemplate(BuildContext context) {
|
||||||
final htmlEditor = PlatformInfo.isWeb
|
if (PlatformInfo.isWeb) {
|
||||||
? _buildHtmlEditorWeb(context, controller.contentHtmlEditor ?? '')
|
return Column(
|
||||||
: _buildHtmlEditor(context, initialContent: controller.contentHtmlEditor ?? '');
|
children: [
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
if (PlatformInfo.isWeb)
|
|
||||||
ToolbarRichTextWebBuilder(
|
ToolbarRichTextWebBuilder(
|
||||||
richTextWebController: controller.richTextWebController!,
|
richTextWebController: controller.richTextWebController!,
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
@@ -424,18 +461,34 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
htmlEditor,
|
_buildHtmlEditorWeb(
|
||||||
Obx(() => Center(
|
context,
|
||||||
child: CompressImageLoadingBarWidget(
|
controller.contentHtmlEditor),
|
||||||
isCompressing: controller.isCompressingInlineImage.value,
|
Obx(() {
|
||||||
),
|
bool isInserting = controller.publicAssetController?.isUploading.isTrue == true
|
||||||
))
|
|| controller.isCompressingInlineImage.isTrue;
|
||||||
]
|
return InsertImageLoadingIndicator(isInserting: isInserting);
|
||||||
),
|
})
|
||||||
],
|
]
|
||||||
);
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
_buildHtmlEditor(
|
||||||
|
context,
|
||||||
|
initialContent: controller.contentHtmlEditor),
|
||||||
|
Obx(() {
|
||||||
|
bool isInserting = controller.publicAssetController?.isUploading.isTrue == true
|
||||||
|
|| controller.isCompressingInlineImage.isTrue;
|
||||||
|
return InsertImageLoadingIndicator(isInserting: isInserting);
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHtmlEditorWeb(BuildContext context, String initContent) {
|
Widget _buildHtmlEditorWeb(BuildContext context, String initContent) {
|
||||||
@@ -447,6 +500,7 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
hint: '',
|
hint: '',
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
initialText: initContent.isEmpty ? null : initContent,
|
initialText: initContent.isEmpty ? null : initContent,
|
||||||
|
disableDragAndDrop: true,
|
||||||
customBodyCssStyle: HtmlUtils.customCssStyleHtmlEditor(direction: AppUtils.getCurrentDirection(context)),
|
customBodyCssStyle: HtmlUtils.customCssStyleHtmlEditor(direction: AppUtils.getCurrentDirection(context)),
|
||||||
),
|
),
|
||||||
htmlToolbarOptions: const html_editor_browser.HtmlToolbarOptions(
|
htmlToolbarOptions: const html_editor_browser.HtmlToolbarOptions(
|
||||||
@@ -463,9 +517,11 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onInit: () {
|
onInit: () {
|
||||||
|
controller.richTextWebController?.editorController.setOnDragDropEvent();
|
||||||
controller.richTextWebController?.editorController.setFullScreen();
|
controller.richTextWebController?.editorController.setFullScreen();
|
||||||
controller.updateContentHtmlEditor(initContent);
|
controller.updateContentHtmlEditor(initContent);
|
||||||
}, onFocus: () {
|
},
|
||||||
|
onFocus: () {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
controller.richTextWebController?.editorController.setFocus();
|
controller.richTextWebController?.editorController.setFocus();
|
||||||
@@ -473,7 +529,9 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController>
|
|||||||
controller.richTextWebController?.closeAllMenuPopup();
|
controller.richTextWebController?.closeAllMenuPopup();
|
||||||
},
|
},
|
||||||
onChangeSelection: controller.richTextWebController?.onEditorSettingsChange,
|
onChangeSelection: controller.richTextWebController?.onEditorSettingsChange,
|
||||||
onChangeCodeview: controller.updateContentHtmlEditor
|
onChangeCodeview: controller.updateContentHtmlEditor,
|
||||||
|
onDragEnter: controller.handleOnDragEnterSignatureEditorWeb,
|
||||||
|
onDragLeave: (_) {}
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
class IdentityCreatorConstants {
|
class IdentityCreatorConstants {
|
||||||
static const int maxKBSizeIdentityInlineImage = 128; // Kilobyte
|
|
||||||
static const double maxWidthInlineImageDesktop = 800; // Pixel
|
static const double maxWidthInlineImageDesktop = 800; // Pixel
|
||||||
static const double maxWidthInlineImageOther = 700; // Pixel
|
static const double maxWidthInlineImageOther = 700; // Pixel
|
||||||
static const String prefixCompressedInlineImageTemp = 'compressed_';
|
static const String prefixCompressedInlineImageTemp = 'compressed_';
|
||||||
static const int qualityCompressedInlineImage = 50; // 0 - 100
|
static const int qualityCompressedInlineImage = 80; // 0 - 100
|
||||||
}
|
}
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:tmail_ui_user/features/base/widget/circle_loading_widget.dart';
|
|
||||||
|
|
||||||
class CompressImageLoadingBarWidget extends StatelessWidget {
|
|
||||||
|
|
||||||
final bool isCompressing;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const CompressImageLoadingBarWidget({
|
|
||||||
super.key,
|
|
||||||
required this.isCompressing,
|
|
||||||
this.padding,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (isCompressing) {
|
|
||||||
return CircleLoadingWidget(padding: padding);
|
|
||||||
} else {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class InsertImageLoadingIndicator extends StatelessWidget {
|
||||||
|
|
||||||
|
final bool isInserting;
|
||||||
|
|
||||||
|
const InsertImageLoadingIndicator({
|
||||||
|
super.key,
|
||||||
|
required this.isInserting
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isInserting) {
|
||||||
|
return const Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: AppColor.primaryColor
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ class CreatePublicAssetSuccessState extends UIState {
|
|||||||
CreatePublicAssetSuccessState(this.publicAsset);
|
CreatePublicAssetSuccessState(this.publicAsset);
|
||||||
|
|
||||||
final PublicAsset publicAsset;
|
final PublicAsset publicAsset;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [publicAsset];
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreatePublicAssetFailureState extends FeatureFailure {
|
class CreatePublicAssetFailureState extends FeatureFailure {
|
||||||
|
|||||||
@@ -47,9 +47,8 @@ class PublicAssetController extends BaseController {
|
|||||||
List<PublicAssetId> newlyPickedPublicAssetIds = [];
|
List<PublicAssetId> newlyPickedPublicAssetIds = [];
|
||||||
final _publicAssetStreamGroup = StreamGroup<Either<Failure, Success>>();
|
final _publicAssetStreamGroup = StreamGroup<Either<Failure, Success>>();
|
||||||
StreamSubscription<Either<Failure, Success>>? _publicAssetStreamSubscription;
|
StreamSubscription<Either<Failure, Success>>? _publicAssetStreamSubscription;
|
||||||
|
final isUploading = false.obs;
|
||||||
|
|
||||||
StreamSubscription<Either<Failure, Success>>? get publicAssetStreamSubscription
|
|
||||||
=> _publicAssetStreamSubscription;
|
|
||||||
Session? get session => arguments?.session;
|
Session? get session => arguments?.session;
|
||||||
AccountId? get accountId => arguments?.accountId;
|
AccountId? get accountId => arguments?.accountId;
|
||||||
Identity? get identity => arguments?.identity;
|
Identity? get identity => arguments?.identity;
|
||||||
@@ -57,7 +56,7 @@ class PublicAssetController extends BaseController {
|
|||||||
void _handleUploadState(Either<Failure, Success> uploadState) {
|
void _handleUploadState(Either<Failure, Success> uploadState) {
|
||||||
log('PublicAssetController::handleUploadState::${uploadState.runtimeType}');
|
log('PublicAssetController::handleUploadState::${uploadState.runtimeType}');
|
||||||
uploadState.fold(
|
uploadState.fold(
|
||||||
(failure) {},
|
(failure) => consumeState(Stream.value(Left(CreatePublicAssetFailureState()))),
|
||||||
(success) {
|
(success) {
|
||||||
log('PublicAssetController::handleUploadState::success::$success');
|
log('PublicAssetController::handleUploadState::success::$success');
|
||||||
if (success is SuccessAttachmentUploadState) {
|
if (success is SuccessAttachmentUploadState) {
|
||||||
@@ -71,7 +70,10 @@ class PublicAssetController extends BaseController {
|
|||||||
final blobId = success.attachment.blobId;
|
final blobId = success.attachment.blobId;
|
||||||
if (session == null
|
if (session == null
|
||||||
|| accountId == null
|
|| accountId == null
|
||||||
|| blobId == null) return;
|
|| blobId == null) {
|
||||||
|
consumeState(Stream.value(Left(CreatePublicAssetFailureState())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
consumeState(_createPublicAssetInteractor.execute(
|
consumeState(_createPublicAssetInteractor.execute(
|
||||||
session!,
|
session!,
|
||||||
@@ -108,17 +110,27 @@ class PublicAssetController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleFailureViewState(Failure failure) {
|
||||||
|
super.handleFailureViewState(failure);
|
||||||
|
if (failure is CreatePublicAssetFailureState) {
|
||||||
|
isUploading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleUploadAttachmentSuccess(UploadAttachmentSuccess success) async {
|
Future<void> _handleUploadAttachmentSuccess(UploadAttachmentSuccess success) async {
|
||||||
await _publicAssetStreamGroup.add(success.uploadAttachment.progressState);
|
await _publicAssetStreamGroup.add(success.uploadAttachment.progressState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleCreatePublicAssetSuccessState(CreatePublicAssetSuccessState success) {
|
void _handleCreatePublicAssetSuccessState(CreatePublicAssetSuccessState success) {
|
||||||
|
isUploading.value = false;
|
||||||
final publicAsset = success.publicAsset;
|
final publicAsset = success.publicAsset;
|
||||||
if (publicAsset.id == null || publicAsset.publicURI == null) return;
|
if (publicAsset.id == null || publicAsset.publicURI == null) return;
|
||||||
|
|
||||||
newlyPickedPublicAssetIds.add(publicAsset.id!);
|
newlyPickedPublicAssetIds.add(publicAsset.id!);
|
||||||
final imageTag = '<img '
|
final imageTag = '<img '
|
||||||
'src="${publicAsset.publicURI!}" '
|
'src="${publicAsset.publicURI!}" '
|
||||||
|
'style="max-width: 100%" '
|
||||||
'public-asset-id="${publicAsset.id!.value}">';
|
'public-asset-id="${publicAsset.id!.value}">';
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
Get
|
Get
|
||||||
@@ -140,6 +152,7 @@ class PublicAssetController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void uploadFileToBlob(PlatformFile platformFile) {
|
void uploadFileToBlob(PlatformFile platformFile) {
|
||||||
|
isUploading.value = true;
|
||||||
_uploadFileToBlobAction(platformFile);
|
_uploadFileToBlobAction(platformFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:model/upload/file_info.dart';
|
import 'package:model/upload/file_info.dart';
|
||||||
import 'package:tmail_ui_user/features/upload/domain/model/mobile_file_upload.dart';
|
import 'package:tmail_ui_user/features/upload/domain/model/mobile_file_upload.dart';
|
||||||
|
|
||||||
@@ -14,4 +15,12 @@ extension FileInfoExtension on FileInfo {
|
|||||||
type: type,
|
type: type,
|
||||||
isInline: true,
|
isInline: true,
|
||||||
isShared: isShared);
|
isShared: isShared);
|
||||||
|
|
||||||
|
PlatformFile toPlatformFile() => PlatformFile(
|
||||||
|
name: fileName,
|
||||||
|
path: filePath,
|
||||||
|
size: fileSize,
|
||||||
|
bytes: bytes,
|
||||||
|
readStream: readStream,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
|
||||||
import 'package:model/upload/file_info.dart';
|
import 'package:model/upload/file_info.dart';
|
||||||
|
|
||||||
extension ListFileInfoExtension on List<FileInfo> {
|
extension ListFileInfoExtension on List<FileInfo> {
|
||||||
List<int> get listSize => map((file) => file.fileSize).toList();
|
List<int> get listSize => map((file) => file.fileSize).toList();
|
||||||
|
|
||||||
num get totalSize => listSize.isEmpty ? 0 : listSize.reduce((sum, size) => sum + size);
|
num get totalSize => listSize.isEmpty ? 0 : listSize.reduce((sum, size) => sum + size);
|
||||||
|
|
||||||
|
List<FileInfo> get listInlineFiles => where((fileInfo) => fileInfo.isInline == true).toList();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
|
||||||
|
extension ListPlatformFileExtensions on List<PlatformFile> {
|
||||||
|
int get totalFilesSize => fold<int>(0, (sum, file) => sum + file.size);
|
||||||
|
}
|
||||||
@@ -4180,4 +4180,11 @@ class AppLocalizations {
|
|||||||
'Mark as spam failed',
|
'Mark as spam failed',
|
||||||
name: 'markAsSpamFailed');
|
name: 'markAsSpamFailed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get canNotUploadFileToSignature {
|
||||||
|
return Intl.message(
|
||||||
|
'Can not upload this file to signature',
|
||||||
|
name: 'canNotUploadFileToSignature'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user