feat(ai-scribe): reorganize abstractions and update dialog modal UI
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:scribe/scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart';
|
||||
|
||||
class ComposerAiScribeSelectionOverlay extends StatelessWidget {
|
||||
const ComposerAiScribeSelectionOverlay({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final ComposerController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!controller.isAIScribeAvailable) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Obx(() {
|
||||
return AiSelectionOverlay(
|
||||
selection: controller.editorTextSelection.value,
|
||||
imagePaths: controller.imagePaths,
|
||||
onAiScribeResultCallback: controller.insertTextInEditor,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnTextSelectionChanged = Function(TextSelectionData?);
|
||||
|
||||
class TextSelectionData {
|
||||
final bool hasSelection;
|
||||
final String? selectedText;
|
||||
final TextSelectionCoordinates? coordinates;
|
||||
|
||||
const TextSelectionData({
|
||||
required this.hasSelection,
|
||||
this.selectedText,
|
||||
this.coordinates,
|
||||
});
|
||||
|
||||
factory TextSelectionData.empty() {
|
||||
return const TextSelectionData(hasSelection: false);
|
||||
}
|
||||
|
||||
factory TextSelectionData.fromMap(Map<dynamic, dynamic> data) {
|
||||
try {
|
||||
final hasSelection = data['hasSelection'] as bool? ?? false;
|
||||
|
||||
if (!hasSelection) {
|
||||
return TextSelectionData.empty();
|
||||
}
|
||||
|
||||
final selectedText = data['selectedText'] as String?;
|
||||
final coordinatesData = data['coordinates'];
|
||||
|
||||
if (coordinatesData != null && selectedText != null) {
|
||||
final coordinates = TextSelectionCoordinates.fromMap(coordinatesData);
|
||||
return TextSelectionData(
|
||||
hasSelection: true,
|
||||
selectedText: selectedText,
|
||||
coordinates: coordinates,
|
||||
);
|
||||
}
|
||||
|
||||
return TextSelectionData.empty();
|
||||
} catch (e) {
|
||||
return TextSelectionData.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextSelectionCoordinates {
|
||||
final double x;
|
||||
final double y;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const TextSelectionCoordinates({
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
factory TextSelectionCoordinates.fromMap(Map<dynamic, dynamic> data) {
|
||||
return TextSelectionCoordinates(
|
||||
x: (data['x'] as num?)?.toDouble() ?? 0.0,
|
||||
y: (data['y'] as num?)?.toDouble() ?? 0.0,
|
||||
width: (data['width'] as num?)?.toDouble() ?? 0.0,
|
||||
height: (data['height'] as num?)?.toDouble() ?? 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
Offset get position => Offset(x, y);
|
||||
}
|
||||
|
||||
class EditorTextSelection {
|
||||
final String? selectedText;
|
||||
final Offset? coordinates;
|
||||
|
||||
const EditorTextSelection({
|
||||
this.selectedText,
|
||||
this.coordinates,
|
||||
});
|
||||
|
||||
bool get hasSelection => selectedText != null && selectedText!.isNotEmpty;
|
||||
}
|
||||
|
||||
mixin TextSelectionMixin<T extends StatefulWidget> on State<T> {
|
||||
OnTextSelectionChanged? get onSelectionChanged => null;
|
||||
|
||||
void handleSelectionChange(Map<dynamic, dynamic> data) {
|
||||
final selectionData = TextSelectionData.fromMap(data);
|
||||
onSelectionChanged?.call(selectionData);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import 'package:core/utils/platform_info.dart';
|
||||
import 'package:core/utils/html/html_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/mixins/text_selection_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/mixin/text_selection_mixin.dart';
|
||||
|
||||
typedef OnCreatedEditorAction = Function(BuildContext context, HtmlEditorApi editorApi, String content);
|
||||
typedef OnLoadCompletedEditorAction = Function(HtmlEditorApi editorApi, WebUri? url);
|
||||
|
||||
+10
-15
@@ -1,6 +1,7 @@
|
||||
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:scribe/scribe/ai/presentation/widgets/button/ai_assistant_button.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/styles/mobile/tablet_bottom_bar_composer_widget_style.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
@@ -14,8 +15,7 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
|
||||
final VoidCallback sendMessageAction;
|
||||
final VoidCallback requestReadReceiptAction;
|
||||
final VoidCallback toggleMarkAsImportantAction;
|
||||
final VoidCallback? onOpenAIScribe;
|
||||
final GlobalKey? aiScribeButtonKey;
|
||||
final OnOpenAiAssistantModal? onOpenAiAssistantModal;
|
||||
|
||||
const TabletBottomBarComposerWidget({
|
||||
super.key,
|
||||
@@ -27,8 +27,7 @@ class TabletBottomBarComposerWidget extends StatelessWidget {
|
||||
required this.sendMessageAction,
|
||||
required this.requestReadReceiptAction,
|
||||
required this.toggleMarkAsImportantAction,
|
||||
this.onOpenAIScribe,
|
||||
this.aiScribeButtonKey,
|
||||
this.onOpenAiAssistantModal,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -75,18 +74,14 @@ 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!,
|
||||
if (onOpenAiAssistantModal != null)
|
||||
AiAssistantButton(
|
||||
imagePaths: imagePaths,
|
||||
margin: const EdgeInsetsDirectional.only(
|
||||
start: TabletBottomBarComposerWidgetStyle.space,
|
||||
),
|
||||
onOpenAiAssistantModal: onOpenAiAssistantModal!,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: TabletBottomBarComposerWidgetStyle.space),
|
||||
TMailButtonWidget.fromIcon(
|
||||
icon: imagePaths.icSaveToDraft,
|
||||
|
||||
@@ -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';
|
||||
@@ -31,9 +32,8 @@ class BottomBarComposerWidget extends StatelessWidget {
|
||||
final VoidCallback toggleMarkAsImportantAction;
|
||||
final VoidCallback saveAsTemplateAction;
|
||||
final VoidCallback onOpenInsertLink;
|
||||
final VoidCallback? onOpenAIScribe;
|
||||
final GlobalKey? aiScribeButtonKey;
|
||||
final OnMenuChanged? onPopupMenuChanged;
|
||||
final OnOpenAiAssistantModal? onOpenAiAssistantModal;
|
||||
|
||||
const BottomBarComposerWidget({
|
||||
super.key,
|
||||
@@ -56,9 +56,8 @@ class BottomBarComposerWidget extends StatelessWidget {
|
||||
required this.toggleMarkAsImportantAction,
|
||||
required this.saveAsTemplateAction,
|
||||
required this.onOpenInsertLink,
|
||||
this.onOpenAIScribe,
|
||||
this.aiScribeButtonKey,
|
||||
this.onPopupMenuChanged,
|
||||
this.onOpenAiAssistantModal,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -131,25 +130,20 @@ class BottomBarComposerWidget extends StatelessWidget {
|
||||
onTapActionCallback: onOpenInsertLink,
|
||||
),
|
||||
),
|
||||
if (onOpenAIScribe != null) ...[
|
||||
const SizedBox(width: BottomBarComposerWidgetStyle.space),
|
||||
if (onOpenAiAssistantModal != null)
|
||||
AbsorbPointer(
|
||||
absorbing: isCodeViewEnabled,
|
||||
child: TMailButtonWidget.fromIcon(
|
||||
key: aiScribeButtonKey,
|
||||
icon: imagePaths.icSparkle,
|
||||
child: AiAssistantButton(
|
||||
imagePaths: imagePaths,
|
||||
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!,
|
||||
: null,
|
||||
margin: const EdgeInsetsDirectional.only(
|
||||
start: BottomBarComposerWidgetStyle.space,
|
||||
),
|
||||
onOpenAiAssistantModal: onOpenAiAssistantModal!,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: BottomBarComposerWidgetStyle.space),
|
||||
PopupMenuOverlayWidget(
|
||||
controller: menuMoreOptionController,
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:core/utils/html/html_template.dart';
|
||||
import 'package:core/utils/html/html_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/mixins/text_selection_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/mixin/text_selection_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/signature_tooltip_widget.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:universal_html/html.dart' hide VoidCallback;
|
||||
|
||||
Reference in New Issue
Block a user