feat(ai-scribe): reorganize abstractions and update dialog modal UI

This commit is contained in:
dab246
2025-12-18 02:51:43 +07:00
committed by Dat H. Pham
parent 7ddccf2e1c
commit 582b1ebe97
70 changed files with 2449 additions and 1352 deletions
@@ -0,0 +1,13 @@
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
mixin AiScribeMixin {
bool isAICapabilitySupported({Session? session, AccountId? accountId}) {
if (accountId == null || session == null) {
return false;
}
final aiCapability = session.getAICapability(accountId);
return aiCapability != null;
}
}
@@ -27,10 +27,12 @@ import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:scribe/scribe.dart';
import 'package:super_tag_editor/tag_editor.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/base/before_reconnect_handler.dart';
import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart';
import 'package:tmail_ui_user/features/base/mixin/ai_scribe_mixin.dart';
import 'package:tmail_ui_user/features/base/mixin/auto_complete_result_mixin.dart';
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_manager.dart';
import 'package:tmail_ui_user/features/base/state/base_ui_state.dart';
@@ -77,7 +79,6 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_em
import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_list_identities_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_selected_identity_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/update_screen_display_mode_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/ai_scribe_in_composer_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/drag_drog_file_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
@@ -90,7 +91,6 @@ import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from
import 'package:tmail_ui_user/features/composer/presentation/widgets/saving_message_dialog_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/saving_template_dialog_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/sending_message_dialog_view.dart';
import 'package:scribe/scribe.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/save_template_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/update_template_email_state.dart';
@@ -103,9 +103,9 @@ import 'package:tmail_ui_user/features/email/presentation/model/composer_argumen
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_by_id_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/validate_premium_storage_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_text_formatting_menu_state_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/validate_premium_storage_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/state/get_all_identities_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
@@ -131,9 +131,12 @@ import 'package:tmail_ui_user/main/universal_import/html_stub.dart' as html;
import 'package:tmail_ui_user/main/utils/app_config.dart';
class ComposerController extends BaseController
with DragDropFileMixin, AutoCompleteResultMixin, EditorViewMixin, AIScribeInComposerMixin
with
DragDropFileMixin,
AutoCompleteResultMixin,
EditorViewMixin,
AiScribeMixin
implements BeforeReconnectHandler {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final networkConnectionController = Get.find<NetworkConnectionController>();
final _beforeReconnectManager = Get.find<BeforeReconnectManager>();
@@ -157,6 +160,7 @@ class ComposerController extends BaseController
final isEmailChanged = Rx<bool>(false);
final isMarkAsImportant = Rx<bool>(false);
final isContentHeightExceeded = Rx<bool>(false);
final editorTextSelection = Rxn<TextSelectionModel>();
final LocalFilePickerInteractor _localFilePickerInteractor;
final LocalImagePickerInteractor _localImagePickerInteractor;
@@ -221,11 +225,8 @@ class ComposerController extends BaseController
StreamSubscription<html.Event>? _subscriptionOnBlur;
StreamSubscription<String>? _composerCacheListener;
@override
RichTextMobileTabletController? richTextMobileTabletController;
@override
RichTextWebController? richTextWebController;
CustomPopupMenuController? menuMoreOptionController;
final ScrollController scrollController = ScrollController();
@@ -262,18 +263,11 @@ class ComposerController extends BaseController
TransformHtmlEmailContentInteractor get transformHtmlEmailContentInteractor => _transformHtmlEmailContentInteractor;
@override
GenerateAITextInteractor get generateAITextInteractor => Get.find<GenerateAITextInteractor>();
String get ownEmailAddress =>
mailboxDashBoardController.ownEmailAddress.value;
@override
Session? get session => mailboxDashBoardController.sessionCurrent;
@override
AccountId? get accountId => mailboxDashBoardController.accountId.value;
late Worker uploadInlineImageWorker;
late bool _isEmailBodyLoaded;
@@ -873,20 +867,6 @@ class ComposerController extends BaseController
}
}
@override
Future<String> getTextOnlyContentInEditor() async {
try {
final htmlContent = await getContentInEditor();
String textContent = StringConvert.convertHtmlContentToTextContent(htmlContent);
return textContent;
} catch (e) {
logError('ComposerController::getTextOnlyContentInEditor:Exception = $e');
return '';
}
}
Future<void> _prepareToSendMessages(BuildContext context) async {
final arguments = composerArguments.value;
final session = mailboxDashBoardController.sessionCurrent;
@@ -8,8 +8,8 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/email/prefix_email_address.dart';
import 'package:scribe/scribe/ai/presentation/widgets/ai_scribe_button.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_content_height_exceeded_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_edit_recipient_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_open_context_menu_extension.dart';
@@ -22,6 +22,7 @@ import 'package:tmail_ui_user/features/composer/presentation/styles/mobile_app_b
import 'package:tmail_ui_user/features/composer/presentation/view/mobile/mobile_container_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/mobile/mobile_editor_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/mobile/tablet_container_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/ai_scribe/composer_ai_scribe_selection_overlay.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/insert_image_loading_bar_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/list_recipients_collapsed_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart';
@@ -264,7 +265,7 @@ class ComposerView extends GetWidget<ComposerController> {
SizedBox(height: MediaQuery.viewInsetsOf(context).bottom + 64),
],
),
_buildAIScribeSelectionButton(context),
ComposerAiScribeSelectionOverlay(controller: controller),
],
)
],
@@ -453,7 +454,7 @@ class ComposerView extends GetWidget<ComposerController> {
SizedBox(height: MediaQuery.viewInsetsOf(context).bottom + 64),
],
),
_buildAIScribeSelectionButton(context),
ComposerAiScribeSelectionOverlay(controller: controller),
],
),
],
@@ -469,8 +470,9 @@ class ComposerView extends GetWidget<ComposerController> {
sendMessageAction: () => controller.handleClickSendButton(context),
requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context),
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
onOpenAIScribe: controller.isAIScribeAvailable ? () => controller.showAIScribeMenuForFullText(context) : null,
aiScribeButtonKey: controller.isAIScribeAvailable ? controller.aiScribeButtonKey : null,
onOpenAiAssistantModal: controller.isAIScribeAvailable
? controller.openAIAssistantModal
: null,
)),
]
)
@@ -553,46 +555,4 @@ class ComposerView extends GetWidget<ComposerController> {
onEnableAllRecipientsInputAction: controller.handleEnableRecipientsInputOnMobileAction,
));
}
Widget _buildAIScribeSelectionButton(BuildContext context) {
if (!controller.isAIScribeAvailable) {
return const SizedBox.shrink();
}
return Obx(() {
final textSelection = controller.editorTextSelection.value;
if (textSelection != null &&
textSelection.hasSelection &&
textSelection.coordinates != null) {
final coordinates = textSelection.coordinates!;
// Account for the horizontal padding around the editor
const editorHorizontalPadding = 12.0;
return PositionedDirectional(
start: coordinates.dx + editorHorizontalPadding,
top: coordinates.dy,
child: Builder(
builder: (buttonContext) {
return AIScribeButton(
imagePaths: controller.imagePaths,
onTap: () {
final RenderBox? renderBox = buttonContext.findRenderObject() as RenderBox?;
if (renderBox != null) {
final position = renderBox.localToGlobal(Offset.zero);
controller.showAIScribeMenuForSelectedText(
context,
buttonPosition: position,
);
} else {
controller.showAIScribeMenuForSelectedText(context);
}
},
);
},
),
);
} else {
return const SizedBox.shrink();
}
});
}
}
@@ -6,16 +6,16 @@ import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/email/prefix_email_address.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:scribe/scribe/ai/presentation/widgets/ai_scribe_button.dart';
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_manager.dart';
import 'package:tmail_ui_user/features/base/widget/dialog_picker/color_dialog_picker.dart';
import 'package:tmail_ui_user/features/base/widget/keyboard/keyboard_handler_wrapper.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/composer_print_draft_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_edit_recipient_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_insert_link_composer_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_recipients_collapsed_extensions.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_recipients_collapsed_extensions.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/mark_as_important_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/preview_upload_file_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/remove_draggable_email_address_between_recipient_fields_extension.dart';
@@ -25,6 +25,7 @@ import 'package:tmail_ui_user/features/composer/presentation/view/web/desktop_re
import 'package:tmail_ui_user/features/composer/presentation/view/web/mobile_responsive_container_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/web/tablet_responsive_container_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/web/web_editor_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/ai_scribe/composer_ai_scribe_selection_overlay.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/insert_image_loading_bar_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/list_recipients_collapsed_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart';
@@ -333,7 +334,7 @@ class ComposerView extends GetWidget<ComposerController> {
return const SizedBox.shrink();
}
}),
_buildAIScribeSelectionButton(context),
ComposerAiScribeSelectionOverlay(controller: controller),
],
),
),
@@ -559,8 +560,9 @@ class ComposerView extends GetWidget<ComposerController> {
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
onOpenInsertLink: controller.openInsertLink,
onOpenAIScribe: controller.isAIScribeAvailable ? () => controller.showAIScribeMenuForFullText(context) : null,
aiScribeButtonKey: controller.isAIScribeAvailable ? controller.aiScribeButtonKey : null,
onOpenAiAssistantModal: controller.isAIScribeAvailable
? controller.openAIAssistantModal
: null,
)),
],
),
@@ -607,7 +609,7 @@ class ComposerView extends GetWidget<ComposerController> {
return const SizedBox.shrink();
}
}),
_buildAIScribeSelectionButton(context),
ComposerAiScribeSelectionOverlay(controller: controller),
],
),
),
@@ -835,8 +837,9 @@ class ComposerView extends GetWidget<ComposerController> {
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
onOpenInsertLink: controller.openInsertLink,
onOpenAIScribe: controller.isAIScribeAvailable ? () => controller.showAIScribeMenuForFullText(context) : null,
aiScribeButtonKey: controller.isAIScribeAvailable ? controller.aiScribeButtonKey : null,
onOpenAiAssistantModal: controller.isAIScribeAvailable
? controller.openAIAssistantModal
: null,
)),
],
),
@@ -882,7 +885,7 @@ class ComposerView extends GetWidget<ComposerController> {
return const SizedBox.shrink();
}
}),
_buildAIScribeSelectionButton(context),
ComposerAiScribeSelectionOverlay(controller: controller),
],
),
)
@@ -955,46 +958,4 @@ class ComposerView extends GetWidget<ComposerController> {
onEnableAllRecipientsInputAction: controller.handleEnableRecipientsInputOnMobileAction,
));
}
Widget _buildAIScribeSelectionButton(BuildContext context) {
if (!controller.isAIScribeAvailable) {
return const SizedBox.shrink();
}
return Obx(() {
final textSelection = controller.editorTextSelection.value;
if (textSelection != null &&
textSelection.hasSelection &&
textSelection.coordinates != null) {
final coordinates = textSelection.coordinates!;
return PositionedDirectional(
start: coordinates.dx,
top: coordinates.dy,
child: PointerInterceptor(
child: Builder(
builder: (buttonContext) {
return AIScribeButton(
imagePaths: controller.imagePaths,
onTap: () {
final RenderBox? renderBox = buttonContext.findRenderObject() as RenderBox?;
if (renderBox != null) {
final position = renderBox.localToGlobal(Offset.zero);
controller.showAIScribeMenuForSelectedText(
context,
buttonPosition: position,
);
} else {
controller.showAIScribeMenuForSelectedText(context);
}
},
);
},
),
),
);
} else {
return const SizedBox.shrink();
}
});
}
}
@@ -0,0 +1,73 @@
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:core/utils/string_convert.dart';
import 'package:flutter/material.dart';
import 'package:scribe/scribe.dart';
import 'package:scribe/scribe/ai/presentation/model/modal/modal_cross_axis_alignment.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/text_selection_mixin.dart';
extension HandleAiScribeInComposerExtension on ComposerController {
bool get isAIScribeAvailable {
return isAICapabilitySupported(
session: mailboxDashBoardController.sessionCurrent,
accountId: mailboxDashBoardController.accountId.value,
);
}
Future<String> _getTextOnlyContentInEditor() async {
try {
final htmlContent = await getContentInEditor();
String textContent = StringConvert.convertHtmlContentToTextContent(
htmlContent,);
return textContent;
} catch (e) {
logError('$runtimeType::getTextOnlyContentInEditor:Exception = $e');
return '';
}
}
void insertTextInEditor(String text) {
final htmlContent = text.replaceAll('\n', '<br>');
if (PlatformInfo.isWeb) {
richTextWebController?.editorController.insertHtml(htmlContent);
} else {
richTextMobileTabletController?.htmlEditorApi?.insertHtml(htmlContent);
}
}
Future<void> openAIAssistantModal(Offset? position, Size? size) async {
final fullText = await _getTextOnlyContentInEditor();
final aiResult = await AiScribeModalManager.showAIScribeMenuModal(
imagePaths: imagePaths,
availableCategories: AIScribeMenuCategory.values,
buttonPosition: position,
buttonSize: size,
content: fullText,
preferredPlacement: ModalPlacement.top,
crossAxisAlignment: ModalCrossAxisAlignment.start,
);
if (aiResult != null) {
insertTextInEditor(aiResult);
}
}
void handleTextSelection(TextSelectionData? textSelectionData) {
if (textSelectionData != null && textSelectionData.hasSelection) {
editorTextSelection.value = TextSelectionModel(
selectedText: textSelectionData.selectedText,
coordinates: textSelectionData.coordinates != null
? Offset(
textSelectionData.coordinates!.x,
textSelectionData.coordinates!.y,
)
: null,
);
} else {
editorTextSelection.value = null;
}
}
}
@@ -1,101 +0,0 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:scribe/scribe.dart';
import 'package:scribe/scribe/ai/presentation/widgets/ai_scribe.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/widgets/mixins/text_selection_mixin.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
mixin AIScribeInComposerMixin {
final editorTextSelection = Rxn<EditorTextSelection>();
final GlobalKey aiScribeButtonKey = GlobalKey();
RichTextWebController? get richTextWebController;
RichTextMobileTabletController? get richTextMobileTabletController;
ImagePaths get imagePaths;
GenerateAITextInteractor get generateAITextInteractor;
Session? get session;
AccountId? get accountId;
bool get isAIScribeAvailable {
if (!PlatformInfo.isWeb) return false;
final currentSession = session;
final currentAccountId = accountId;
if (currentSession == null || currentAccountId == null) return false;
final aiCapability = currentSession.getAICapability(currentAccountId);
return aiCapability != null;
}
Future<String> getTextOnlyContentInEditor();
void insertTextInEditor(String text) {
final htmlContent = text.replaceAll('\n', '<br>');
if (PlatformInfo.isWeb) {
richTextWebController?.editorController.insertHtml(htmlContent);
} else {
richTextMobileTabletController?.htmlEditorApi?.insertHtml(htmlContent);
}
}
void showAIScribeMenuForFullText(BuildContext context) async {
final fullText = await getTextOnlyContentInEditor();
final RenderBox? renderBox = aiScribeButtonKey.currentContext?.findRenderObject() as RenderBox?;
Offset? buttonPosition;
if (renderBox != null) {
buttonPosition = renderBox.localToGlobal(Offset.zero);
}
if (!context.mounted) return;
showAIScribeDialog(
context: context,
imagePaths: imagePaths,
content: fullText,
onInsertText: insertTextInEditor,
interactor: generateAITextInteractor,
buttonPosition: buttonPosition,
);
}
void showAIScribeMenuForSelectedText(BuildContext context, {Offset? buttonPosition}) {
final selection = editorTextSelection.value?.selectedText;
if (selection == null || selection.isEmpty) {
return;
}
showAIScribeDialog(
context: context,
imagePaths: imagePaths,
content: selection,
onInsertText: insertTextInEditor,
interactor: generateAITextInteractor,
buttonPosition: buttonPosition,
);
}
void handleTextSelection(TextSelectionData? textSelectionData) {
if (textSelectionData != null && textSelectionData.hasSelection) {
editorTextSelection.value = EditorTextSelection(
selectedText: textSelectionData.selectedText,
coordinates: textSelectionData.coordinates != null
? Offset(
textSelectionData.coordinates!.x,
textSelectionData.coordinates!.y,
)
: null,
);
} else {
editorTextSelection.value = null;
}
}
}
@@ -69,18 +69,6 @@ class TextSelectionCoordinates {
Offset get position => Offset(x, y);
}
class EditorTextSelection {
final String? selectedText;
final Offset? coordinates;
const EditorTextSelection({
this.selectedText,
this.coordinates,
});
bool get hasSelection => selectedText != null && selectedText!.isNotEmpty;
}
mixin TextSelectionMixin<T extends StatefulWidget> on State<T> {
OnTextSelectionChanged? get onSelectionChanged => null;
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:scribe/scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart';
class ComposerAiScribeSelectionOverlay extends StatelessWidget {
const ComposerAiScribeSelectionOverlay({
super.key,
required this.controller,
});
final ComposerController controller;
@override
Widget build(BuildContext context) {
if (!controller.isAIScribeAvailable) {
return const SizedBox.shrink();
}
return Obx(() {
return AiSelectionOverlay(
selection: controller.editorTextSelection.value,
imagePaths: controller.imagePaths,
onAiScribeResultCallback: controller.insertTextInEditor,
);
});
}
}
@@ -5,7 +5,7 @@ import 'package:core/utils/platform_info.dart';
import 'package:core/utils/html/html_utils.dart';
import 'package:flutter/material.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mixins/text_selection_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/text_selection_mixin.dart';
typedef OnCreatedEditorAction = Function(BuildContext context, HtmlEditorApi editorApi, String content);
typedef OnLoadCompletedEditorAction = Function(HtmlEditorApi editorApi, WebUri? url);
@@ -1,6 +1,7 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:scribe/scribe/ai/presentation/widgets/button/ai_assistant_button.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/mobile/tablet_bottom_bar_composer_widget_style.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -14,8 +15,7 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
final VoidCallback sendMessageAction;
final VoidCallback requestReadReceiptAction;
final VoidCallback toggleMarkAsImportantAction;
final VoidCallback? onOpenAIScribe;
final GlobalKey? aiScribeButtonKey;
final OnOpenAiAssistantModal? onOpenAiAssistantModal;
const TabletBottomBarComposerWidget({
super.key,
@@ -27,8 +27,7 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
required this.sendMessageAction,
required this.requestReadReceiptAction,
required this.toggleMarkAsImportantAction,
this.onOpenAIScribe,
this.aiScribeButtonKey,
this.onOpenAiAssistantModal,
});
@override
@@ -75,18 +74,14 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
: AppLocalizations.of(context).turnOnRequestReadReceipt,
onTapActionCallback: requestReadReceiptAction,
),
if (onOpenAIScribe != null) ...[
const SizedBox(width: TabletBottomBarComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
key: aiScribeButtonKey,
icon: imagePaths.icSparkle,
borderRadius: TabletBottomBarComposerWidgetStyle.iconRadius,
padding: TabletBottomBarComposerWidgetStyle.iconPadding,
iconSize: TabletBottomBarComposerWidgetStyle.iconSize,
tooltipMessage: AppLocalizations.of(context).aiAssistant,
onTapActionCallback: onOpenAIScribe!,
if (onOpenAiAssistantModal != null)
AiAssistantButton(
imagePaths: imagePaths,
margin: const EdgeInsetsDirectional.only(
start: TabletBottomBarComposerWidgetStyle.space,
),
onOpenAiAssistantModal: onOpenAiAssistantModal!,
),
],
const SizedBox(width: TabletBottomBarComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
icon: imagePaths.icSaveToDraft,
@@ -4,6 +4,7 @@ import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/platform_info.dart';
import 'package:custom_pop_up_menu/custom_pop_up_menu.dart';
import 'package:flutter/material.dart';
import 'package:scribe/scribe/ai/presentation/widgets/button/ai_assistant_button.dart';
import 'package:tmail_ui_user/features/base/widget/highlight_svg_icon_on_hover.dart';
import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu_overlay_widget.dart';
@@ -31,9 +32,8 @@ class BottomBarComposerWidget extends StatelessWidget {
final VoidCallback toggleMarkAsImportantAction;
final VoidCallback saveAsTemplateAction;
final VoidCallback onOpenInsertLink;
final VoidCallback? onOpenAIScribe;
final GlobalKey? aiScribeButtonKey;
final OnMenuChanged? onPopupMenuChanged;
final OnOpenAiAssistantModal? onOpenAiAssistantModal;
const BottomBarComposerWidget({
super.key,
@@ -56,9 +56,8 @@ class BottomBarComposerWidget extends StatelessWidget {
required this.toggleMarkAsImportantAction,
required this.saveAsTemplateAction,
required this.onOpenInsertLink,
this.onOpenAIScribe,
this.aiScribeButtonKey,
this.onPopupMenuChanged,
this.onOpenAiAssistantModal,
});
@override
@@ -131,25 +130,20 @@ class BottomBarComposerWidget extends StatelessWidget {
onTapActionCallback: onOpenInsertLink,
),
),
if (onOpenAIScribe != null) ...[
const SizedBox(width: BottomBarComposerWidgetStyle.space),
if (onOpenAiAssistantModal != null)
AbsorbPointer(
absorbing: isCodeViewEnabled,
child: TMailButtonWidget.fromIcon(
key: aiScribeButtonKey,
icon: imagePaths.icSparkle,
child: AiAssistantButton(
imagePaths: imagePaths,
iconColor: isCodeViewEnabled
? BottomBarComposerWidgetStyle.disabledIconColor
: BottomBarComposerWidgetStyle.iconColor,
borderRadius: BottomBarComposerWidgetStyle.iconRadius,
backgroundColor: Colors.transparent,
padding: BottomBarComposerWidgetStyle.iconPadding,
iconSize: BottomBarComposerWidgetStyle.iconSize,
tooltipMessage: AppLocalizations.of(context).aiAssistant,
onTapActionCallback: onOpenAIScribe!,
: null,
margin: const EdgeInsetsDirectional.only(
start: BottomBarComposerWidgetStyle.space,
),
onOpenAiAssistantModal: onOpenAiAssistantModal!,
),
),
],
const SizedBox(width: BottomBarComposerWidgetStyle.space),
PopupMenuOverlayWidget(
controller: menuMoreOptionController,
@@ -6,7 +6,7 @@ import 'package:core/utils/html/html_template.dart';
import 'package:core/utils/html/html_utils.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mixins/text_selection_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/text_selection_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/signature_tooltip_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:universal_html/html.dart' hide VoidCallback;