feat(ai-scribe): Improve UI for AI Scribe suggestion modal
This commit is contained in:
@@ -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);
|
||||
|
||||
+19
-7
@@ -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<void> 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 {
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@ class ComposerAiScribeSelectionOverlay extends StatelessWidget {
|
||||
return AiSelectionOverlay(
|
||||
selection: controller.editorTextSelection.value,
|
||||
imagePaths: controller.imagePaths,
|
||||
onAiScribeResultCallback: controller.insertTextInEditor,
|
||||
onSelectAiScribeSuggestionAction:
|
||||
controller.handleAiScribeSuggestionAction,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String?> showAIScribeMenuModal({
|
||||
static Future<void> showAIScribeMenuModal({
|
||||
required ImagePaths imagePaths,
|
||||
required List<AIScribeMenuCategory> 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<String?> showAIScribeSuggestionModal({
|
||||
static Future<void> showAIScribeSuggestionModal({
|
||||
required AIAction aiAction,
|
||||
required ImagePaths imagePaths,
|
||||
required OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction,
|
||||
String? content,
|
||||
Offset? buttonPosition,
|
||||
Size? buttonSize,
|
||||
}) {
|
||||
return Get.dialog<String?>(
|
||||
ModalPlacement? preferredPlacement,
|
||||
ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center,
|
||||
}) async {
|
||||
await Get.dialog<String?>(
|
||||
AiScribeSuggestionWidget(
|
||||
aiAction: aiAction,
|
||||
imagePaths: imagePaths,
|
||||
content: content,
|
||||
buttonPosition: buttonPosition,
|
||||
buttonSize: buttonSize,
|
||||
preferredPlacement: preferredPlacement,
|
||||
crossAxisAlignment: crossAxisAlignment,
|
||||
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
|
||||
),
|
||||
barrierColor: AIScribeColors.dialogBarrier,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ class AiScribeSuggestionLoading extends StatelessWidget {
|
||||
children: [
|
||||
_SparklePulseIcon(
|
||||
asset: imagePaths.icSparkle,
|
||||
color: AppColor.blueFF00B7FF,
|
||||
color: AppColor.blue00B7FF,
|
||||
),
|
||||
Expanded(
|
||||
child: _AnimatedEllipsisText(
|
||||
|
||||
+4
-4
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
+51
-17
@@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user