Open AI Scribe from bottom bar button
This commit is contained in:
@@ -27,6 +27,7 @@ import 'package:model/model.dart';
|
|||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
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:scribe/scribe/ai/presentation/widgets/ai_scribe.dart';
|
||||||
import 'package:super_tag_editor/tag_editor.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/base_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/base/before_reconnect_handler.dart';
|
import 'package:tmail_ui_user/features/base/before_reconnect_handler.dart';
|
||||||
@@ -89,6 +90,7 @@ 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_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/saving_template_dialog_view.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/sending_message_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/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/save_template_email_state.dart';
|
||||||
import 'package:tmail_ui_user/features/email/domain/state/update_template_email_state.dart';
|
import 'package:tmail_ui_user/features/email/domain/state/update_template_email_state.dart';
|
||||||
@@ -198,6 +200,7 @@ class ComposerController extends BaseController
|
|||||||
final GlobalKey<TagsEditorState> keyReplyToEmailTagEditor = GlobalKey<TagsEditorState>();
|
final GlobalKey<TagsEditorState> keyReplyToEmailTagEditor = GlobalKey<TagsEditorState>();
|
||||||
final GlobalKey headerEditorMobileWidgetKey = GlobalKey();
|
final GlobalKey headerEditorMobileWidgetKey = GlobalKey();
|
||||||
final GlobalKey<DropdownButton2State> identityDropdownKey = GlobalKey<DropdownButton2State>();
|
final GlobalKey<DropdownButton2State> identityDropdownKey = GlobalKey<DropdownButton2State>();
|
||||||
|
final GlobalKey aiScribeButtonKey = GlobalKey();
|
||||||
final double defaultPaddingCoordinateYCursorEditor = 8;
|
final double defaultPaddingCoordinateYCursorEditor = 8;
|
||||||
|
|
||||||
FocusNode? subjectEmailInputFocusNode;
|
FocusNode? subjectEmailInputFocusNode;
|
||||||
@@ -859,6 +862,57 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getTextOnlyContentInEditor() async {
|
||||||
|
try {
|
||||||
|
final htmlContent = await getContentInEditor();
|
||||||
|
|
||||||
|
String textContent = htmlContent.replaceAll(RegExp(r'<[^>]*>'), '');
|
||||||
|
|
||||||
|
textContent = textContent
|
||||||
|
.replaceAll(' ', ' ')
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll(''', "'");
|
||||||
|
|
||||||
|
return textContent.trim();
|
||||||
|
} catch (e) {
|
||||||
|
logError('ComposerController::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
buttonPosition: buttonPosition,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _prepareToSendMessages(BuildContext context) async {
|
Future<void> _prepareToSendMessages(BuildContext context) async {
|
||||||
final arguments = composerArguments.value;
|
final arguments = composerArguments.value;
|
||||||
final session = mailboxDashBoardController.sessionCurrent;
|
final session = mailboxDashBoardController.sessionCurrent;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_c
|
|||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/subject_composer_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/subject_composer_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/widgets/view_entire_message_with_message_clipped_widget.dart';
|
import 'package:tmail_ui_user/features/email/presentation/widgets/view_entire_message_with_message_clipped_widget.dart';
|
||||||
|
import 'package:scribe/scribe.dart';
|
||||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
|
|
||||||
class ComposerView extends GetWidget<ComposerController> {
|
class ComposerView extends GetWidget<ComposerController> {
|
||||||
@@ -448,6 +449,8 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
sendMessageAction: () => controller.handleClickSendButton(context),
|
sendMessageAction: () => controller.handleClickSendButton(context),
|
||||||
requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context),
|
requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context),
|
||||||
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
||||||
|
onOpenAIScribe: () => controller.showAIScribeMenuForFullText(context),
|
||||||
|
aiScribeButtonKey: controller.aiScribeButtonKey,
|
||||||
)),
|
)),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import 'package:tmail_ui_user/features/composer/presentation/widgets/list_recipi
|
|||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/subject_composer_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/subject_composer_widget.dart';
|
||||||
|
import 'package:scribe/scribe.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/attachment_composer_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/attachment_composer_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/attachment_drop_zone_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/attachment_drop_zone_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart';
|
||||||
@@ -555,6 +556,8 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
||||||
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
||||||
onOpenInsertLink: controller.openInsertLink,
|
onOpenInsertLink: controller.openInsertLink,
|
||||||
|
onOpenAIScribe: () => controller.showAIScribeMenuForFullText(context),
|
||||||
|
aiScribeButtonKey: controller.aiScribeButtonKey,
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -827,6 +830,8 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
||||||
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
||||||
onOpenInsertLink: controller.openInsertLink,
|
onOpenInsertLink: controller.openInsertLink,
|
||||||
|
onOpenAIScribe: () => controller.showAIScribeMenuForFullText(context),
|
||||||
|
aiScribeButtonKey: controller.aiScribeButtonKey,
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
+16
@@ -14,6 +14,8 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
|
|||||||
final VoidCallback sendMessageAction;
|
final VoidCallback sendMessageAction;
|
||||||
final VoidCallback requestReadReceiptAction;
|
final VoidCallback requestReadReceiptAction;
|
||||||
final VoidCallback toggleMarkAsImportantAction;
|
final VoidCallback toggleMarkAsImportantAction;
|
||||||
|
final VoidCallback? onOpenAIScribe;
|
||||||
|
final GlobalKey? aiScribeButtonKey;
|
||||||
|
|
||||||
const TabletBottomBarComposerWidget({
|
const TabletBottomBarComposerWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -25,6 +27,8 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
|
|||||||
required this.sendMessageAction,
|
required this.sendMessageAction,
|
||||||
required this.requestReadReceiptAction,
|
required this.requestReadReceiptAction,
|
||||||
required this.toggleMarkAsImportantAction,
|
required this.toggleMarkAsImportantAction,
|
||||||
|
this.onOpenAIScribe,
|
||||||
|
this.aiScribeButtonKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -71,6 +75,18 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
|
|||||||
: AppLocalizations.of(context).turnOnRequestReadReceipt,
|
: AppLocalizations.of(context).turnOnRequestReadReceipt,
|
||||||
onTapActionCallback: requestReadReceiptAction,
|
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!,
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(width: TabletBottomBarComposerWidgetStyle.space),
|
const SizedBox(width: TabletBottomBarComposerWidgetStyle.space),
|
||||||
TMailButtonWidget.fromIcon(
|
TMailButtonWidget.fromIcon(
|
||||||
icon: imagePaths.icSaveToDraft,
|
icon: imagePaths.icSaveToDraft,
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class BottomBarComposerWidget extends StatelessWidget {
|
|||||||
final VoidCallback toggleMarkAsImportantAction;
|
final VoidCallback toggleMarkAsImportantAction;
|
||||||
final VoidCallback saveAsTemplateAction;
|
final VoidCallback saveAsTemplateAction;
|
||||||
final VoidCallback onOpenInsertLink;
|
final VoidCallback onOpenInsertLink;
|
||||||
|
final VoidCallback? onOpenAIScribe;
|
||||||
|
final GlobalKey? aiScribeButtonKey;
|
||||||
final OnMenuChanged? onPopupMenuChanged;
|
final OnMenuChanged? onPopupMenuChanged;
|
||||||
|
|
||||||
const BottomBarComposerWidget({
|
const BottomBarComposerWidget({
|
||||||
@@ -54,6 +56,8 @@ class BottomBarComposerWidget extends StatelessWidget {
|
|||||||
required this.toggleMarkAsImportantAction,
|
required this.toggleMarkAsImportantAction,
|
||||||
required this.saveAsTemplateAction,
|
required this.saveAsTemplateAction,
|
||||||
required this.onOpenInsertLink,
|
required this.onOpenInsertLink,
|
||||||
|
this.onOpenAIScribe,
|
||||||
|
this.aiScribeButtonKey,
|
||||||
this.onPopupMenuChanged,
|
this.onPopupMenuChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,6 +131,25 @@ class BottomBarComposerWidget extends StatelessWidget {
|
|||||||
onTapActionCallback: onOpenInsertLink,
|
onTapActionCallback: onOpenInsertLink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (onOpenAIScribe != null) ...[
|
||||||
|
const SizedBox(width: BottomBarComposerWidgetStyle.space),
|
||||||
|
AbsorbPointer(
|
||||||
|
absorbing: isCodeViewEnabled,
|
||||||
|
child: TMailButtonWidget.fromIcon(
|
||||||
|
key: aiScribeButtonKey,
|
||||||
|
icon: imagePaths.icSparkle,
|
||||||
|
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!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(width: BottomBarComposerWidgetStyle.space),
|
const SizedBox(width: BottomBarComposerWidgetStyle.space),
|
||||||
PopupMenuOverlayWidget(
|
PopupMenuOverlayWidget(
|
||||||
controller: menuMoreOptionController,
|
controller: menuMoreOptionController,
|
||||||
|
|||||||
@@ -5473,5 +5473,11 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"aiAssistant": "Assistant IA",
|
||||||
|
"@aiAssistant": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"@@last_modified": "2025-12-10T16:32:00.611716",
|
"@@last_modified": "2025-12-10T10:22:35.345340",
|
||||||
"initializing_data": "Initializing data...",
|
"initializing_data": "Initializing data...",
|
||||||
"@initializing_data": {
|
"@initializing_data": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -5129,5 +5129,11 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"aiAssistant": "AI assistant",
|
||||||
|
"@aiAssistant": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5491,5 +5491,11 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"aiAssistant": "Помощник ИИ",
|
||||||
|
"@aiAssistant": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5479,5 +5479,11 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"aiAssistant": "Trợ lý AI",
|
||||||
|
"@aiAssistant": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5436,4 +5436,11 @@ class AppLocalizations {
|
|||||||
name: 'startToAddFavoritesEmails',
|
name: 'startToAddFavoritesEmails',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get aiAssistant {
|
||||||
|
return Intl.message(
|
||||||
|
'AI assistant',
|
||||||
|
name: 'aiAssistant',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"@@last_modified": "2025-12-10T16:32:03.004307",
|
"@@last_modified": "2025-12-10T10:22:36.990738",
|
||||||
"categoryCorrectGrammar": "Correct grammar",
|
"categoryCorrectGrammar": "Correct grammar",
|
||||||
"@categoryCorrectGrammar": {
|
"@categoryCorrectGrammar": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
Reference in New Issue
Block a user