feat(ai-scribe): Fix the English locale file `(intl_en.arb)` is missing all three AI Scribe keys
feat(ai-scribe): Fix async function declared without proper return type annotation. feat(ai-scribe): Fix Async operations not properly awaited. feat(ai-scribe): Fix inconsistent capitalization between button labels. feat(ai-scribe): Fix GenerateAITextFailure check will never match. feat(ai-scribe): Fix consider validating text input for predefined actions. feat(ai-scribe): Using a subshell for directory isolation. feat(ai-scribe): Fix verify the mock usage or document intent. feat(ai-scribe): Using a more conventional import for Offset. feat(ai-scribe): Adding error handling for consistency feat(ai-scribe): Remove `fromJson` and `toJson` of AIResponse feat(ai-scribe): Using constants for role values. feat(ai-scribe): Using a more specific import. feat(ai-scribe): Using Timer for cleaner lifecycle management feat(ai-scribe): Adding error handling to fromMap() for consistency. feat(ai-scribe): Disposing the ValueNotifier feat(ai-scribe): Redundant null check after assignment feat(ai-scribe): Fix calling `registerSelectionChangeListener` multiple times may be inefficient. feat(ai-scribe): Safer type handling for JavaScript callback args feat(ai-scribe): Fix async callbacks not awaited in switch statement. feat(ai-scribe): Fix binding lifecycle mismatch for GetAIScribeConfigInteractor feat(ai-scribe): Fix ai prompts feat(ai-scribe): Fix unawaited async call to `_setupSelectionListener` feat(ai-scribe): Fix switch cases use top-origin coordinates while `PositionedDirectional(bottom:)`` expects bottom-origin coordinates. feat(ai-scribe): Adding error handling for selection listener setup.
This commit is contained in:
+8
-9
@@ -35,7 +35,7 @@ extension HandleAiScribeInComposerExtension on ComposerController {
|
||||
}
|
||||
}
|
||||
|
||||
void insertTextInEditor(String text) async {
|
||||
Future<void> insertTextInEditor(String text) async {
|
||||
try {
|
||||
final htmlContent = StringConvert.convertTextContentToHtmlContent(text);
|
||||
|
||||
@@ -87,18 +87,17 @@ extension HandleAiScribeInComposerExtension on ComposerController {
|
||||
// Ensure we only insert at cursor position by collapsing selection before inserting
|
||||
Future<void> onInsertTextCallback(String text) async {
|
||||
await collapseSelection();
|
||||
|
||||
insertTextInEditor(text);
|
||||
await insertTextInEditor(text);
|
||||
}
|
||||
|
||||
// If there is a selection, it will replace the selection, else it will replace everything
|
||||
void onReplaceTextCallback(String text) {
|
||||
Future<void> onReplaceTextCallback(String text) async {
|
||||
final selection = editorTextSelection.value?.selectedText;
|
||||
if (selection == null || selection.isEmpty) {
|
||||
clearTextInEditor();
|
||||
}
|
||||
|
||||
insertTextInEditor(text);
|
||||
await insertTextInEditor(text);
|
||||
}
|
||||
|
||||
Future<void> openAIAssistantModal(Offset? position, Size? size) async {
|
||||
@@ -116,16 +115,16 @@ extension HandleAiScribeInComposerExtension on ComposerController {
|
||||
);
|
||||
}
|
||||
|
||||
void handleAiScribeSuggestionAction(
|
||||
Future<void> handleAiScribeSuggestionAction(
|
||||
AiScribeSuggestionActions action,
|
||||
String suggestionText,
|
||||
) {
|
||||
) async {
|
||||
switch (action) {
|
||||
case AiScribeSuggestionActions.replace:
|
||||
onReplaceTextCallback(suggestionText);
|
||||
await onReplaceTextCallback(suggestionText);
|
||||
break;
|
||||
case AiScribeSuggestionActions.insert:
|
||||
onInsertTextCallback(suggestionText);
|
||||
await onInsertTextCallback(suggestionText);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,12 +58,21 @@ class TextSelectionCoordinates {
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
try {
|
||||
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,
|
||||
);
|
||||
} catch (e) {
|
||||
return const TextSelectionCoordinates(
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Offset get position => Offset(x, y);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
import 'package:core/presentation/constants/constants_ui.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/html/html_template.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:core/utils/html/html_utils.dart';
|
||||
@@ -36,7 +37,6 @@ class MobileEditorWidget extends StatefulWidget {
|
||||
|
||||
class _MobileEditorState extends State<MobileEditorWidget> with TextSelectionMixin {
|
||||
|
||||
HtmlEditorApi? _editorApi;
|
||||
late String _createdViewId;
|
||||
|
||||
@override
|
||||
@@ -48,9 +48,8 @@ class _MobileEditorState extends State<MobileEditorWidget> with TextSelectionMix
|
||||
@override
|
||||
void Function(TextSelectionData?)? get onSelectionChanged => widget.onTextSelectionChanged;
|
||||
|
||||
void _setupSelectionListener() async {
|
||||
final webViewController = _editorApi?.webViewController;
|
||||
if (webViewController == null) return;
|
||||
Future<void> _setupSelectionListener(HtmlEditorApi editorApi) async {
|
||||
final webViewController = editorApi.webViewController;
|
||||
|
||||
final registerSelectionChange =
|
||||
HtmlUtils.registerSelectionChangeListener(_createdViewId);
|
||||
@@ -61,8 +60,10 @@ class _MobileEditorState extends State<MobileEditorWidget> with TextSelectionMix
|
||||
if (!mounted) return;
|
||||
|
||||
if (args.isNotEmpty) {
|
||||
final data = args[0] as Map<dynamic, dynamic>;
|
||||
handleSelectionChange(data);
|
||||
final rawData = args[0];
|
||||
if (rawData is Map<dynamic, dynamic>) {
|
||||
handleSelectionChange(rawData);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -72,10 +73,13 @@ class _MobileEditorState extends State<MobileEditorWidget> with TextSelectionMix
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_editorApi = null;
|
||||
super.dispose();
|
||||
Future<void> _onWebViewCreated(HtmlEditorApi editorApi) async {
|
||||
widget.onCreatedEditorAction.call(context, editorApi, widget.content);
|
||||
try {
|
||||
await _setupSelectionListener(editorApi);
|
||||
} catch (e) {
|
||||
logError('Error onWebViewCreated: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -90,11 +94,7 @@ class _MobileEditorState extends State<MobileEditorWidget> with TextSelectionMix
|
||||
direction: widget.direction,
|
||||
useDefaultFontStyle: true,
|
||||
),
|
||||
onCreated: (editorApi) {
|
||||
_editorApi = editorApi;
|
||||
widget.onCreatedEditorAction.call(context, editorApi, widget.content);
|
||||
_setupSelectionListener();
|
||||
},
|
||||
onCreated: _onWebViewCreated,
|
||||
onCompleted: widget.onLoadCompletedEditorAction,
|
||||
onContentHeightChanged: PlatformInfo.isIOS ? widget.onEditorContentHeightChanged : null,
|
||||
);
|
||||
|
||||
@@ -92,6 +92,7 @@ class _WebEditorState extends State<WebEditorWidget> with TextSelectionMixin {
|
||||
bool _signatureTooltipReady = false;
|
||||
|
||||
late String _createdViewId;
|
||||
late WebScript _selectionChangeScript;
|
||||
|
||||
@override
|
||||
void Function(TextSelectionData?)? get onSelectionChanged => widget.onTextSelectionChanged;
|
||||
@@ -103,7 +104,11 @@ class _WebEditorState extends State<WebEditorWidget> with TextSelectionMixin {
|
||||
_editorController = widget.editorController;
|
||||
|
||||
final registerSelectionChange =
|
||||
HtmlUtils.registerSelectionChangeListener(_createdViewId);
|
||||
HtmlUtils.registerSelectionChangeListener(_createdViewId);
|
||||
_selectionChangeScript = WebScript(
|
||||
name: registerSelectionChange.name,
|
||||
script: registerSelectionChange.script,
|
||||
);
|
||||
|
||||
_editorListener = (event) {
|
||||
try {
|
||||
@@ -112,7 +117,7 @@ class _WebEditorState extends State<WebEditorWidget> with TextSelectionMixin {
|
||||
|
||||
if (data['name'] == HtmlUtils.registerDropListener.name) {
|
||||
_editorController.evaluateJavascriptWeb(HtmlUtils.removeLineHeight1px.name);
|
||||
} else if (data['name'] == registerSelectionChange.name
|
||||
} else if (data['name'] == _selectionChangeScript.name
|
||||
&& data['viewId'] == _createdViewId) {
|
||||
handleSelectionChange(data);
|
||||
}
|
||||
@@ -123,9 +128,8 @@ class _WebEditorState extends State<WebEditorWidget> with TextSelectionMixin {
|
||||
);
|
||||
}
|
||||
};
|
||||
if (_editorListener != null) {
|
||||
window.addEventListener("message", _editorListener!);
|
||||
}
|
||||
|
||||
window.addEventListener("message", _editorListener!);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -189,8 +193,8 @@ class _WebEditorState extends State<WebEditorWidget> with TextSelectionMixin {
|
||||
script: HtmlUtils.unregisterDropListener.script,
|
||||
),
|
||||
WebScript(
|
||||
name: HtmlUtils.registerSelectionChangeListener(_createdViewId).name,
|
||||
script: HtmlUtils.registerSelectionChangeListener(_createdViewId).script,
|
||||
name: _selectionChangeScript.name,
|
||||
script: _selectionChangeScript.script,
|
||||
),
|
||||
WebScript(
|
||||
name: HtmlUtils.collapseSelectionToEnd.name,
|
||||
|
||||
+1
-1
@@ -309,7 +309,7 @@ class MailboxDashBoardController extends ReloadableController
|
||||
final isPopupMenuOpened = RxBool(false);
|
||||
final octetsQuota = Rxn<Quota>();
|
||||
final isTextFormattingMenuOpened = RxBool(false);
|
||||
final cachedAIScribeConfig = Rx<AIScribeConfig>(AIScribeConfig.initial());
|
||||
final cachedAIScribeConfig = AIScribeConfig.initial().obs;
|
||||
|
||||
Map<Role, MailboxId> mapDefaultMailboxIdByRole = {};
|
||||
Map<MailboxId, PresentationMailbox> mapMailboxById = {};
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_
|
||||
import 'package:tmail_ui_user/features/manage_account/data/local/preferences_setting_manager.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/data/repository/manage_account_repository_impl.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_ai_scribe_config_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/save_language_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/server_settings/data/datasource/server_settings_data_source.dart';
|
||||
import 'package:tmail_ui_user/features/server_settings/data/datasource_impl/remote_server_settings_data_source_impl.dart';
|
||||
@@ -102,6 +101,5 @@ class PreferencesInteractorsBindings extends InteractorsBindings {
|
||||
Get.delete<ManageAccountRepositoryImpl>(tag: composerId);
|
||||
Get.delete<ManageAccountRepository>(tag: composerId);
|
||||
Get.delete<SaveLanguageInteractor>(tag: composerId);
|
||||
Get.delete<GetAIScribeConfigInteractor>(tag: composerId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user