diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 80b197661..de885f90a 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -278,7 +278,8 @@ extension AppColor on Color { static const m3Primary = Color(0xFF0A84FF); static const m3Primary95 = Color(0xFFE3F1FF); static const gray49454F = Color(0xFF49454F); - static const blueFF00B7FF = Color(0xFF00B7FF); + static const blue00B7FF = Color(0xFF00B7FF); + static const blueD2E9FF = Color(0xFFD2E9FF); static const lightGrayF9FAFB = Color(0xFFF9FAFB); static const black4D4D4D = Color(0xFF4D4D4D); static const black1A1A1A = Color(0xFF1A1A1A); diff --git a/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart b/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart index 36cf3dd35..1390ab995 100644 --- a/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart +++ b/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart @@ -3,6 +3,7 @@ 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/context_menu/ai_scribe_suggestion_actions.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'; @@ -19,7 +20,8 @@ extension HandleAiScribeInComposerExtension on ComposerController { try { final htmlContent = await getContentInEditor(); String textContent = StringConvert.convertHtmlContentToTextContent( - htmlContent,); + htmlContent, + ); return textContent; } catch (e) { logError('$runtimeType::getTextOnlyContentInEditor:Exception = $e'); @@ -40,7 +42,7 @@ extension HandleAiScribeInComposerExtension on ComposerController { Future openAIAssistantModal(Offset? position, Size? size) async { final fullText = await _getTextOnlyContentInEditor(); - final aiResult = await AiScribeModalManager.showAIScribeMenuModal( + await AiScribeModalManager.showAIScribeMenuModal( imagePaths: imagePaths, availableCategories: AIScribeMenuCategory.values, buttonPosition: position, @@ -48,10 +50,20 @@ extension HandleAiScribeInComposerExtension on ComposerController { content: fullText, preferredPlacement: ModalPlacement.top, crossAxisAlignment: ModalCrossAxisAlignment.start, + onSelectAiScribeSuggestionAction: handleAiScribeSuggestionAction, ); + } - if (aiResult != null) { - insertTextInEditor(aiResult); + void handleAiScribeSuggestionAction( + AiScribeSuggestionActions action, + String suggestionText, + ) { + switch(action) { + case AiScribeSuggestionActions.replace: + break; + case AiScribeSuggestionActions.insert: + insertTextInEditor(suggestionText); + break; } } @@ -61,9 +73,9 @@ extension HandleAiScribeInComposerExtension on ComposerController { selectedText: textSelectionData.selectedText, coordinates: textSelectionData.coordinates != null ? Offset( - textSelectionData.coordinates!.x, - textSelectionData.coordinates!.y, - ) + textSelectionData.coordinates!.x, + textSelectionData.coordinates!.y, + ) : null, ); } else { diff --git a/lib/features/composer/presentation/widgets/ai_scribe/composer_ai_scribe_selection_overlay.dart b/lib/features/composer/presentation/widgets/ai_scribe/composer_ai_scribe_selection_overlay.dart index 9f6445296..89f060601 100644 --- a/lib/features/composer/presentation/widgets/ai_scribe/composer_ai_scribe_selection_overlay.dart +++ b/lib/features/composer/presentation/widgets/ai_scribe/composer_ai_scribe_selection_overlay.dart @@ -22,7 +22,8 @@ class ComposerAiScribeSelectionOverlay extends StatelessWidget { return AiSelectionOverlay( selection: controller.editorTextSelection.value, imagePaths: controller.imagePaths, - onAiScribeResultCallback: controller.insertTextInEditor, + onSelectAiScribeSuggestionAction: + controller.handleAiScribeSuggestionAction, ); }); } diff --git a/scribe/lib/scribe/ai/presentation/model/context_menu/ai_scribe_suggestion_actions.dart b/scribe/lib/scribe/ai/presentation/model/context_menu/ai_scribe_suggestion_actions.dart new file mode 100644 index 000000000..1093259ce --- /dev/null +++ b/scribe/lib/scribe/ai/presentation/model/context_menu/ai_scribe_suggestion_actions.dart @@ -0,0 +1,15 @@ +import 'package:scribe/scribe/ai/localizations/scribe_localizations.dart'; + +enum AiScribeSuggestionActions { + replace, + insert; + + String getLabel(ScribeLocalizations localizations) { + switch (this) { + case AiScribeSuggestionActions.replace: + return localizations.replace; + case AiScribeSuggestionActions.insert: + return localizations.insert; + } + } +} \ No newline at end of file diff --git a/scribe/lib/scribe/ai/presentation/utils/modal/ai_scribe_modal_manager.dart b/scribe/lib/scribe/ai/presentation/utils/modal/ai_scribe_modal_manager.dart index 26aca76bb..b1eaddcbe 100644 --- a/scribe/lib/scribe/ai/presentation/utils/modal/ai_scribe_modal_manager.dart +++ b/scribe/lib/scribe/ai/presentation/utils/modal/ai_scribe_modal_manager.dart @@ -9,13 +9,15 @@ import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart'; import 'package:scribe/scribe/ai/presentation/utils/context_menu/popup_submenu_controller.dart'; import 'package:scribe/scribe/ai/presentation/widgets/modal/ai_scribe_modal_widget.dart'; import 'package:scribe/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart'; +import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart'; class AiScribeModalManager { AiScribeModalManager._(); - static Future showAIScribeMenuModal({ + static Future showAIScribeMenuModal({ required ImagePaths imagePaths, required List availableCategories, + required OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction, String? content, Offset? buttonPosition, Size? buttonSize, @@ -39,32 +41,39 @@ class AiScribeModalManager { ).whenComplete(submenuController.hide); if (aiAction != null) { - return await showAIScribeSuggestionModal( + await showAIScribeSuggestionModal( aiAction: aiAction, imagePaths: imagePaths, content: content, buttonPosition: buttonPosition, buttonSize: buttonSize, + preferredPlacement: preferredPlacement, + crossAxisAlignment: crossAxisAlignment, + onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction, ); } - - return null; } - static Future showAIScribeSuggestionModal({ + static Future showAIScribeSuggestionModal({ required AIAction aiAction, required ImagePaths imagePaths, + required OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction, String? content, Offset? buttonPosition, Size? buttonSize, - }) { - return Get.dialog( + ModalPlacement? preferredPlacement, + ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center, + }) async { + await Get.dialog( AiScribeSuggestionWidget( aiAction: aiAction, imagePaths: imagePaths, content: content, buttonPosition: buttonPosition, buttonSize: buttonSize, + preferredPlacement: preferredPlacement, + crossAxisAlignment: crossAxisAlignment, + onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction, ), barrierColor: AIScribeColors.dialogBarrier, ); diff --git a/scribe/lib/scribe/ai/presentation/widgets/button/inline_ai_assist_button.dart b/scribe/lib/scribe/ai/presentation/widgets/button/inline_ai_assist_button.dart index 8bdd11ba9..b4f26f4be 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/button/inline_ai_assist_button.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/button/inline_ai_assist_button.dart @@ -4,17 +4,17 @@ import 'package:flutter/material.dart'; import 'package:scribe/scribe/ai/presentation/model/ai_scribe_menu_action.dart'; import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart'; import 'package:scribe/scribe/ai/presentation/utils/modal/ai_scribe_modal_manager.dart'; -import 'package:scribe/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart'; +import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart'; class InlineAiAssistButton extends StatelessWidget { final ImagePaths imagePaths; final String? selectedText; - final OnAiScribeResultCallback onAiScribeResultCallback; + final OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction; const InlineAiAssistButton({ super.key, required this.imagePaths, - required this.onAiScribeResultCallback, + required this.onSelectAiScribeSuggestionAction, this.selectedText, }); @@ -39,20 +39,17 @@ class InlineAiAssistButton extends StatelessWidget { Size? size; if (renderBox != null && renderBox is RenderBox) { - position = renderBox.localToGlobal(Offset.zero); - size = renderBox.size; + position = renderBox.localToGlobal(Offset.zero); + size = renderBox.size; } - final aiResult = await AiScribeModalManager.showAIScribeMenuModal( + await AiScribeModalManager.showAIScribeMenuModal( imagePaths: imagePaths, availableCategories: AIScribeMenuCategory.values, buttonPosition: position, content: selectedText, buttonSize: size, + onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction, ); - - if (aiResult != null) { - onAiScribeResultCallback(aiResult); - } } } diff --git a/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart b/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart index 987d15aeb..071e44a5b 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart @@ -18,8 +18,7 @@ import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_header.dart'; import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart'; import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart'; - -typedef OnAiScribeResultCallback = void Function(String result); +import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart'; class AiScribeSuggestionWidget extends StatefulWidget { final AIAction aiAction; @@ -29,11 +28,13 @@ class AiScribeSuggestionWidget extends StatefulWidget { final Size? buttonSize; final ModalPlacement? preferredPlacement; final ModalCrossAxisAlignment crossAxisAlignment; + final OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction; const AiScribeSuggestionWidget({ super.key, required this.aiAction, required this.imagePaths, + required this.onSelectAiScribeSuggestionAction, this.content, this.buttonPosition, this.buttonSize, @@ -123,7 +124,7 @@ class _AiScribeSuggestionWidgetModalState return AiScribeSuggestionSuccess( imagePaths: widget.imagePaths, suggestionText: value.response.result, - aiAction: widget.aiAction, + onSelectAction: widget.onSelectAiScribeSuggestionAction, ); } else if (value is GenerateAITextFailure) { return AiScribeSuggestionError(imagePaths: widget.imagePaths); diff --git a/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart b/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart index dacfe3f82..ec609b74e 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart @@ -22,7 +22,7 @@ class AiScribeSuggestionLoading extends StatelessWidget { children: [ _SparklePulseIcon( asset: imagePaths.icSparkle, - color: AppColor.blueFF00B7FF, + color: AppColor.blue00B7FF, ), Expanded( child: _AnimatedEllipsisText( diff --git a/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart b/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart index 83a773521..08ad134b9 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart @@ -1,19 +1,18 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; -import 'package:scribe/scribe.dart'; import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart'; import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart'; class AiScribeSuggestionSuccess extends StatelessWidget { final ImagePaths imagePaths; final String suggestionText; - final AIAction aiAction; + final OnSelectAiScribeSuggestionAction onSelectAction; const AiScribeSuggestionSuccess({ super.key, required this.imagePaths, required this.suggestionText, - required this.aiAction, + required this.onSelectAction, }); @override @@ -23,6 +22,7 @@ class AiScribeSuggestionSuccess extends StatelessWidget { child: Column( spacing: 8, mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( child: Text( @@ -33,7 +33,7 @@ class AiScribeSuggestionSuccess extends StatelessWidget { AiScribeSuggestionSuccessListActions( imagePaths: imagePaths, suggestionText: suggestionText, - aiAction: aiAction, + onSelectAction: onSelectAction, ), ], ), diff --git a/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart b/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart index 6e151a42e..05ec252c3 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart @@ -1,36 +1,70 @@ -import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/core.dart'; +import 'package:core/presentation/views/dialog/confirm_dialog_button.dart'; import 'package:flutter/material.dart'; -import 'package:scribe/scribe.dart'; -import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart'; +import 'package:scribe/scribe/ai/localizations/scribe_localizations.dart'; +import 'package:scribe/scribe/ai/presentation/model/context_menu/ai_scribe_suggestion_actions.dart'; + +typedef OnSelectAiScribeSuggestionAction = void Function( + AiScribeSuggestionActions action, + String suggestionText, +); class AiScribeSuggestionSuccessListActions extends StatelessWidget { final ImagePaths imagePaths; final String suggestionText; - final AIAction aiAction; + final OnSelectAiScribeSuggestionAction onSelectAction; const AiScribeSuggestionSuccessListActions({ super.key, required this.imagePaths, required this.suggestionText, - required this.aiAction, + required this.onSelectAction, }); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsetsDirectional.only(bottom: 8), - child: Column( - spacing: 8, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Text( - suggestionText, - style: AIScribeTextStyles.suggestionContent, + final localizations = ScribeLocalizations.of(context); + + return Row( + mainAxisAlignment: MainAxisAlignment.end, + spacing: 8, + children: [ + Flexible( + child: Container( + constraints: const BoxConstraints(minWidth: 67), + height: 36, + child: ConfirmDialogButton( + label: AiScribeSuggestionActions.replace.getLabel(localizations), + textColor: AppColor.primaryMain, + onTapAction: () { + Navigator.of(context).pop(); + onSelectAction( + AiScribeSuggestionActions.replace, + suggestionText, + ); + }, ), ), - ], - ), + ), + Flexible( + child: Container( + constraints: const BoxConstraints(minWidth: 72), + height: 36, + child: ConfirmDialogButton( + label: AiScribeSuggestionActions.insert.getLabel(localizations), + backgroundColor: AppColor.blueD2E9FF, + textColor: AppColor.primaryMain, + onTapAction: () { + Navigator.of(context).pop(); + onSelectAction( + AiScribeSuggestionActions.insert, + suggestionText, + ); + }, + ), + ), + ), + ], ); } } diff --git a/scribe/lib/scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart b/scribe/lib/scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart index 76623ea20..b55e9b0a0 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart @@ -3,19 +3,19 @@ import 'package:flutter/material.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:scribe/scribe/ai/presentation/model/text_selection_model.dart'; import 'package:scribe/scribe/ai/presentation/widgets/button/inline_ai_assist_button.dart'; -import 'package:scribe/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart'; +import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart'; class AiSelectionOverlay extends StatelessWidget { const AiSelectionOverlay({ super.key, required this.selection, required this.imagePaths, - required this.onAiScribeResultCallback, + required this.onSelectAiScribeSuggestionAction, }); final TextSelectionModel? selection; final ImagePaths imagePaths; - final OnAiScribeResultCallback onAiScribeResultCallback; + final OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction; @override Widget build(BuildContext context) { @@ -35,7 +35,7 @@ class AiSelectionOverlay extends StatelessWidget { child: InlineAiAssistButton( imagePaths: imagePaths, selectedText: selectedText, - onAiScribeResultCallback: onAiScribeResultCallback, + onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction, ), ), );