diff --git a/core/lib/utils/html/html_utils.dart b/core/lib/utils/html/html_utils.dart index 614cb97ea..872ca9ddf 100644 --- a/core/lib/utils/html/html_utils.dart +++ b/core/lib/utils/html/html_utils.dart @@ -220,9 +220,12 @@ class HtmlUtils { const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { window._savedRange = selection.getRangeAt(0).cloneRange(); - return selection.toString(); + const result = selection.toString() + window.parent.postMessage(JSON.stringify({ "type": "toDart: saveSelection", result }), "*"); + return result; } delete window._savedRange; + window.parent.postMessage(JSON.stringify({ "type": "toDart: saveSelection", result: "" }), "*"); return ""; })();''', name: 'saveSelection'); @@ -236,9 +239,12 @@ class HtmlUtils { selection.removeAllRanges(); selection.addRange(window._savedRange); delete window._savedRange; - return selection.toString(); + const result = selection.toString() + window.parent.postMessage(JSON.stringify({ "type": "toDart: restoreSelection", result }), "*"); + return result; } } + window.parent.postMessage(JSON.stringify({ "type": "toDart: restoreSelection", result: "" }), "*"); return ""; })();''', name: 'restoreSelection'); @@ -247,8 +253,11 @@ class HtmlUtils { script: ''' (() => { if(window._savedRange) { - return window._savedRange.toString(); + const result = window._savedRange.toString(); + window.parent.postMessage(JSON.stringify({ "type": "toDart: getSavedSelection", result }), "*"); + return result; } else { + window.parent.postMessage(JSON.stringify({ "type": "toDart: getSavedSelection", result: "" }), "*"); return ""; } })();''', diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 41c3682f6..ecfb57db9 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -134,6 +134,9 @@ class ComposerView extends GetWidget { saveToDraftsAction: () => controller.handleClickSaveAsDraftsButton(context), saveToTemplateAction: () => controller.handleClickSaveAsTemplateButton(context), deleteComposerAction: controller.handleClickDeleteComposer, + onOpenAiAssistantModal: controller.isAIScribeAvailable + ? controller.openAIAssistantModal + : null, )), ConstrainedBox( constraints: BoxConstraints( diff --git a/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart b/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart index 936fdef2f..89b92b696 100644 --- a/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart @@ -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'; @@ -32,6 +33,7 @@ class MobileResponsiveAppBarComposerWidget extends StatelessWidget { final VoidCallback saveToTemplateAction; final VoidCallback deleteComposerAction; final VoidCallback toggleMarkAsImportantAction; + final OnOpenAiAssistantModal? onOpenAiAssistantModal; const MobileResponsiveAppBarComposerWidget({ super.key, @@ -55,6 +57,7 @@ class MobileResponsiveAppBarComposerWidget extends StatelessWidget { required this.saveToTemplateAction, required this.deleteComposerAction, required this.toggleMarkAsImportantAction, + this.onOpenAiAssistantModal, }); @override @@ -74,6 +77,14 @@ class MobileResponsiveAppBarComposerWidget extends StatelessWidget { onTapActionCallback: onCloseViewAction ), const Spacer(), + if (onOpenAiAssistantModal != null) + AiAssistantButton( + imagePaths: imagePaths, + margin: const EdgeInsetsDirectional.only( + end: MobileAppBarComposerWidgetStyle.space, + ), + onOpenAiAssistantModal: onOpenAiAssistantModal!, + ), TMailButtonWidget.fromIcon( icon: imagePaths.icRichToolbar, padding: MobileAppBarComposerWidgetStyle.richTextIconPadding, diff --git a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart index b825e19fb..e675f0faa 100644 --- a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart +++ b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart @@ -205,6 +205,22 @@ class _WebEditorState extends State with TextSelectionMixin { name: HtmlUtils.deleteSelectionContent.name, script: HtmlUtils.deleteSelectionContent.script, ), + WebScript( + name: HtmlUtils.saveSelection.name, + script: HtmlUtils.saveSelection.script, + ), + WebScript( + name: HtmlUtils.restoreSelection.name, + script: HtmlUtils.restoreSelection.script, + ), + WebScript( + name: HtmlUtils.getSavedSelection.name, + script: HtmlUtils.getSavedSelection.script, + ), + WebScript( + name: HtmlUtils.clearSavedSelection.name, + script: HtmlUtils.clearSavedSelection.script, + ), WebScript( name: HtmlUtils.recalculateEditorHeight(maxHeight: maxHeight).name, script: HtmlUtils.recalculateEditorHeight(maxHeight: maxHeight).script, diff --git a/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart b/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart index 693505012..411392b8f 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; 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:pointer_interceptor/pointer_interceptor.dart'; import 'package:scribe/scribe.dart'; class AiScribeMobileActionsBottomSheet extends StatefulWidget { @@ -196,39 +197,41 @@ class _AiScribeMobileActionsBottomSheetState final hasContent = widget.content?.isNotEmpty ?? false; - return Container( - height: double.infinity, - decoration: const BoxDecoration( - color: AIScribeColors.background, - ), - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildHeader(context, localizations), - _buildTextCard(context), - if(hasContent) - Flexible( - child: ValueListenableBuilder( - valueListenable: _selectedCategory, - builder: (context, selectedCategory, _) { - return selectedCategory == null - ? _buildMenuListView(menuActions) - : _buildSubmenuListView(); - }, + return PointerInterceptor( + child: Container( + height: double.infinity, + decoration: const BoxDecoration( + color: AIScribeColors.background, + ), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeader(context, localizations), + _buildTextCard(context), + if(hasContent) + Flexible( + child: ValueListenableBuilder( + valueListenable: _selectedCategory, + builder: (context, selectedCategory, _) { + return selectedCategory == null + ? _buildMenuListView(menuActions) + : _buildSubmenuListView(); + }, + ), ), - ), - ], + ], + ), ), - ), - _buildBottomBar(context), - ], + _buildBottomBar(context), + ], + ), ), ), ); diff --git a/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_suggestion_bottom_sheet.dart b/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_suggestion_bottom_sheet.dart index 7a3a3528a..08c05286d 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_suggestion_bottom_sheet.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_suggestion_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:scribe/scribe.dart'; class AiScribeMobileSuggestionBottomSheet extends StatefulWidget { @@ -41,27 +42,29 @@ class _AiScribeMobileSuggestionBottomSheetState Widget build(BuildContext context) { final localizations = ScribeLocalizations.of(context); - return Container( - height: double.infinity, - decoration: const BoxDecoration( - color: AIScribeColors.background, - ), - child: SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: AIScribeSizes.suggestionHeaderPadding, - child: AiScribeSuggestionHeader( - title: aiAction.getLabel(localizations), - imagePaths: imagePaths, + return PointerInterceptor( + child: Container( + height: double.infinity, + decoration: const BoxDecoration( + color: AIScribeColors.background, + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: AIScribeSizes.suggestionHeaderPadding, + child: AiScribeSuggestionHeader( + title: aiAction.getLabel(localizations), + imagePaths: imagePaths, + ), ), - ), - Flexible( - child: buildStateContent(context), - ), - ], + Flexible( + child: buildStateContent(context), + ), + ], + ), ), ), );