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: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(' ', ' ')
|
||||
.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 {
|
||||
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,
|
||||
)),
|
||||
],
|
||||
),
|
||||
|
||||
+16
@@ -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,
|
||||
|
||||
@@ -5473,5 +5473,11 @@
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"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": {
|
||||
"type": "text",
|
||||
@@ -5129,5 +5129,11 @@
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"aiAssistant": "AI assistant",
|
||||
"@aiAssistant": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
@@ -5491,5 +5491,11 @@
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"aiAssistant": "Помощник ИИ",
|
||||
"@aiAssistant": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
Reference in New Issue
Block a user