Open AI Scribe from bottom bar button

This commit is contained in:
Théo Poizat
2025-12-04 17:12:24 +01:00
committed by Dat H. Pham
parent b122928a38
commit 96c95866c6
11 changed files with 134 additions and 2 deletions
@@ -27,6 +27,7 @@ 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/ai/presentation/widgets/ai_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';
@@ -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_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';
@@ -198,6 +200,7 @@ class ComposerController extends BaseController
final GlobalKey<TagsEditorState> keyReplyToEmailTagEditor = GlobalKey<TagsEditorState>();
final GlobalKey headerEditorMobileWidgetKey = GlobalKey();
final GlobalKey<DropdownButton2State> identityDropdownKey = GlobalKey<DropdownButton2State>();
final GlobalKey aiScribeButtonKey = GlobalKey();
final double defaultPaddingCoordinateYCursorEditor = 8;
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('&nbsp;', ' ')
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot;', '"')
.replaceAll('&#39;', "'");
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 {
final arguments = composerArguments.value;
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/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:scribe/scribe.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class ComposerView extends GetWidget<ComposerController> {
@@ -448,6 +449,8 @@ class ComposerView extends GetWidget<ComposerController> {
sendMessageAction: () => controller.handleClickSendButton(context),
requestReadReceiptAction: () => controller.toggleRequestReadReceipt(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/recipient_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_drop_zone_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),
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
onOpenInsertLink: controller.openInsertLink,
onOpenAIScribe: () => controller.showAIScribeMenuForFullText(context),
aiScribeButtonKey: controller.aiScribeButtonKey,
)),
],
),
@@ -827,6 +830,8 @@ class ComposerView extends GetWidget<ComposerController> {
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
onOpenInsertLink: controller.openInsertLink,
onOpenAIScribe: () => controller.showAIScribeMenuForFullText(context),
aiScribeButtonKey: controller.aiScribeButtonKey,
)),
],
),
@@ -14,6 +14,8 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
final VoidCallback sendMessageAction;
final VoidCallback requestReadReceiptAction;
final VoidCallback toggleMarkAsImportantAction;
final VoidCallback? onOpenAIScribe;
final GlobalKey? aiScribeButtonKey;
const TabletBottomBarComposerWidget({
super.key,
@@ -25,6 +27,8 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
required this.sendMessageAction,
required this.requestReadReceiptAction,
required this.toggleMarkAsImportantAction,
this.onOpenAIScribe,
this.aiScribeButtonKey,
});
@override
@@ -71,6 +75,18 @@ 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!,
),
],
const SizedBox(width: TabletBottomBarComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
icon: imagePaths.icSaveToDraft,
@@ -31,6 +31,8 @@ class BottomBarComposerWidget extends StatelessWidget {
final VoidCallback toggleMarkAsImportantAction;
final VoidCallback saveAsTemplateAction;
final VoidCallback onOpenInsertLink;
final VoidCallback? onOpenAIScribe;
final GlobalKey? aiScribeButtonKey;
final OnMenuChanged? onPopupMenuChanged;
const BottomBarComposerWidget({
@@ -54,6 +56,8 @@ class BottomBarComposerWidget extends StatelessWidget {
required this.toggleMarkAsImportantAction,
required this.saveAsTemplateAction,
required this.onOpenInsertLink,
this.onOpenAIScribe,
this.aiScribeButtonKey,
this.onPopupMenuChanged,
});
@@ -127,6 +131,25 @@ class BottomBarComposerWidget extends StatelessWidget {
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),
PopupMenuOverlayWidget(
controller: menuMoreOptionController,
+6
View File
@@ -5473,5 +5473,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"aiAssistant": "Assistant IA",
"@aiAssistant": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
+7 -1
View File
@@ -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": {
"type": "text",
@@ -5129,5 +5129,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"aiAssistant": "AI assistant",
"@aiAssistant": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
+6
View File
@@ -5491,5 +5491,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"aiAssistant": "Помощник ИИ",
"@aiAssistant": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
+6
View File
@@ -5479,5 +5479,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"aiAssistant": "Trợ lý AI",
"@aiAssistant": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -5436,4 +5436,11 @@ class AppLocalizations {
name: 'startToAddFavoritesEmails',
);
}
String get aiAssistant {
return Intl.message(
'AI assistant',
name: 'aiAssistant',
);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2025-12-10T16:32:03.004307",
"@@last_modified": "2025-12-10T10:22:36.990738",
"categoryCorrectGrammar": "Correct grammar",
"@categoryCorrectGrammar": {
"type": "text",