feat(ai-scribe): reorganize abstractions and update dialog modal UI
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:scribe/scribe.dart';
|
||||
import 'package:scribe/scribe/ai/presentation/widgets/ai_scribe.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/mixins/text_selection_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
|
||||
|
||||
mixin AIScribeInComposerMixin {
|
||||
final editorTextSelection = Rxn<EditorTextSelection>();
|
||||
final GlobalKey aiScribeButtonKey = GlobalKey();
|
||||
|
||||
RichTextWebController? get richTextWebController;
|
||||
RichTextMobileTabletController? get richTextMobileTabletController;
|
||||
ImagePaths get imagePaths;
|
||||
GenerateAITextInteractor get generateAITextInteractor;
|
||||
Session? get session;
|
||||
AccountId? get accountId;
|
||||
|
||||
bool get isAIScribeAvailable {
|
||||
if (!PlatformInfo.isWeb) return false;
|
||||
|
||||
final currentSession = session;
|
||||
final currentAccountId = accountId;
|
||||
|
||||
if (currentSession == null || currentAccountId == null) return false;
|
||||
|
||||
final aiCapability = currentSession.getAICapability(currentAccountId);
|
||||
return aiCapability != null;
|
||||
}
|
||||
|
||||
Future<String> getTextOnlyContentInEditor();
|
||||
|
||||
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,
|
||||
interactor: generateAITextInteractor,
|
||||
buttonPosition: buttonPosition,
|
||||
);
|
||||
}
|
||||
|
||||
void showAIScribeMenuForSelectedText(BuildContext context, {Offset? buttonPosition}) {
|
||||
final selection = editorTextSelection.value?.selectedText;
|
||||
if (selection == null || selection.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
showAIScribeDialog(
|
||||
context: context,
|
||||
imagePaths: imagePaths,
|
||||
content: selection,
|
||||
onInsertText: insertTextInEditor,
|
||||
interactor: generateAITextInteractor,
|
||||
buttonPosition: buttonPosition,
|
||||
);
|
||||
}
|
||||
|
||||
void handleTextSelection(TextSelectionData? textSelectionData) {
|
||||
if (textSelectionData != null && textSelectionData.hasSelection) {
|
||||
editorTextSelection.value = EditorTextSelection(
|
||||
selectedText: textSelectionData.selectedText,
|
||||
coordinates: textSelectionData.coordinates != null
|
||||
? Offset(
|
||||
textSelectionData.coordinates!.x,
|
||||
textSelectionData.coordinates!.y,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
} else {
|
||||
editorTextSelection.value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user