Skip to content

Commit f994698

Browse files
committed
feat(ai-scribe): Improve UI for AI Scribe suggestion modal
1 parent e8ffce0 commit f994698

File tree

11 files changed

+125
-55
lines changed

11 files changed

+125
-55
lines changed

core/lib/presentation/extensions/color_extension.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ extension AppColor on Color {
278278
static const m3Primary = Color(0xFF0A84FF);
279279
static const m3Primary95 = Color(0xFFE3F1FF);
280280
static const gray49454F = Color(0xFF49454F);
281-
static const blueFF00B7FF = Color(0xFF00B7FF);
281+
static const blue00B7FF = Color(0xFF00B7FF);
282+
static const blueD2E9FF = Color(0xFFD2E9FF);
282283
static const lightGrayF9FAFB = Color(0xFFF9FAFB);
283284
static const black4D4D4D = Color(0xFF4D4D4D);
284285
static const black1A1A1A = Color(0xFF1A1A1A);

lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:core/utils/platform_info.dart';
33
import 'package:core/utils/string_convert.dart';
44
import 'package:flutter/material.dart';
55
import 'package:scribe/scribe.dart';
6+
import 'package:scribe/scribe/ai/presentation/model/context_menu/ai_scribe_suggestion_actions.dart';
67
import 'package:scribe/scribe/ai/presentation/model/modal/modal_cross_axis_alignment.dart';
78
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
89
import 'package:tmail_ui_user/features/composer/presentation/mixin/text_selection_mixin.dart';
@@ -19,7 +20,8 @@ extension HandleAiScribeInComposerExtension on ComposerController {
1920
try {
2021
final htmlContent = await getContentInEditor();
2122
String textContent = StringConvert.convertHtmlContentToTextContent(
22-
htmlContent,);
23+
htmlContent,
24+
);
2325
return textContent;
2426
} catch (e) {
2527
logError('$runtimeType::getTextOnlyContentInEditor:Exception = $e');
@@ -40,18 +42,28 @@ extension HandleAiScribeInComposerExtension on ComposerController {
4042
Future<void> openAIAssistantModal(Offset? position, Size? size) async {
4143
final fullText = await _getTextOnlyContentInEditor();
4244

43-
final aiResult = await AiScribeModalManager.showAIScribeMenuModal(
45+
await AiScribeModalManager.showAIScribeMenuModal(
4446
imagePaths: imagePaths,
4547
availableCategories: AIScribeMenuCategory.values,
4648
buttonPosition: position,
4749
buttonSize: size,
4850
content: fullText,
4951
preferredPlacement: ModalPlacement.top,
5052
crossAxisAlignment: ModalCrossAxisAlignment.start,
53+
onSelectAiScribeSuggestionAction: handleAiScribeSuggestionAction,
5154
);
55+
}
5256

53-
if (aiResult != null) {
54-
insertTextInEditor(aiResult);
57+
void handleAiScribeSuggestionAction(
58+
AiScribeSuggestionActions action,
59+
String suggestionText,
60+
) {
61+
switch(action) {
62+
case AiScribeSuggestionActions.replace:
63+
break;
64+
case AiScribeSuggestionActions.insert:
65+
insertTextInEditor(suggestionText);
66+
break;
5567
}
5668
}
5769

@@ -61,9 +73,9 @@ extension HandleAiScribeInComposerExtension on ComposerController {
6173
selectedText: textSelectionData.selectedText,
6274
coordinates: textSelectionData.coordinates != null
6375
? Offset(
64-
textSelectionData.coordinates!.x,
65-
textSelectionData.coordinates!.y,
66-
)
76+
textSelectionData.coordinates!.x,
77+
textSelectionData.coordinates!.y,
78+
)
6779
: null,
6880
);
6981
} else {

lib/features/composer/presentation/widgets/ai_scribe/composer_ai_scribe_selection_overlay.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class ComposerAiScribeSelectionOverlay extends StatelessWidget {
2222
return AiSelectionOverlay(
2323
selection: controller.editorTextSelection.value,
2424
imagePaths: controller.imagePaths,
25-
onAiScribeResultCallback: controller.insertTextInEditor,
25+
onSelectAiScribeSuggestionAction:
26+
controller.handleAiScribeSuggestionAction,
2627
);
2728
});
2829
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'package:scribe/scribe/ai/localizations/scribe_localizations.dart';
2+
3+
enum AiScribeSuggestionActions {
4+
replace,
5+
insert;
6+
7+
String getLabel(ScribeLocalizations localizations) {
8+
switch (this) {
9+
case AiScribeSuggestionActions.replace:
10+
return localizations.replace;
11+
case AiScribeSuggestionActions.insert:
12+
return localizations.insert;
13+
}
14+
}
15+
}

scribe/lib/scribe/ai/presentation/utils/modal/ai_scribe_modal_manager.dart

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart';
99
import 'package:scribe/scribe/ai/presentation/utils/context_menu/popup_submenu_controller.dart';
1010
import 'package:scribe/scribe/ai/presentation/widgets/modal/ai_scribe_modal_widget.dart';
1111
import 'package:scribe/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart';
12+
import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart';
1213

1314
class AiScribeModalManager {
1415
AiScribeModalManager._();
1516

16-
static Future<String?> showAIScribeMenuModal({
17+
static Future<void> showAIScribeMenuModal({
1718
required ImagePaths imagePaths,
1819
required List<AIScribeMenuCategory> availableCategories,
20+
required OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction,
1921
String? content,
2022
Offset? buttonPosition,
2123
Size? buttonSize,
@@ -39,32 +41,39 @@ class AiScribeModalManager {
3941
).whenComplete(submenuController.hide);
4042

4143
if (aiAction != null) {
42-
return await showAIScribeSuggestionModal(
44+
await showAIScribeSuggestionModal(
4345
aiAction: aiAction,
4446
imagePaths: imagePaths,
4547
content: content,
4648
buttonPosition: buttonPosition,
4749
buttonSize: buttonSize,
50+
preferredPlacement: preferredPlacement,
51+
crossAxisAlignment: crossAxisAlignment,
52+
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
4853
);
4954
}
50-
51-
return null;
5255
}
5356

54-
static Future<String?> showAIScribeSuggestionModal({
57+
static Future<void> showAIScribeSuggestionModal({
5558
required AIAction aiAction,
5659
required ImagePaths imagePaths,
60+
required OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction,
5761
String? content,
5862
Offset? buttonPosition,
5963
Size? buttonSize,
60-
}) {
61-
return Get.dialog<String?>(
64+
ModalPlacement? preferredPlacement,
65+
ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center,
66+
}) async {
67+
await Get.dialog<String?>(
6268
AiScribeSuggestionWidget(
6369
aiAction: aiAction,
6470
imagePaths: imagePaths,
6571
content: content,
6672
buttonPosition: buttonPosition,
6773
buttonSize: buttonSize,
74+
preferredPlacement: preferredPlacement,
75+
crossAxisAlignment: crossAxisAlignment,
76+
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
6877
),
6978
barrierColor: AIScribeColors.dialogBarrier,
7079
);

scribe/lib/scribe/ai/presentation/widgets/button/inline_ai_assist_button.dart

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import 'package:flutter/material.dart';
44
import 'package:scribe/scribe/ai/presentation/model/ai_scribe_menu_action.dart';
55
import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart';
66
import 'package:scribe/scribe/ai/presentation/utils/modal/ai_scribe_modal_manager.dart';
7-
import 'package:scribe/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart';
7+
import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart';
88

99
class InlineAiAssistButton extends StatelessWidget {
1010
final ImagePaths imagePaths;
1111
final String? selectedText;
12-
final OnAiScribeResultCallback onAiScribeResultCallback;
12+
final OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction;
1313

1414
const InlineAiAssistButton({
1515
super.key,
1616
required this.imagePaths,
17-
required this.onAiScribeResultCallback,
17+
required this.onSelectAiScribeSuggestionAction,
1818
this.selectedText,
1919
});
2020

@@ -39,20 +39,17 @@ class InlineAiAssistButton extends StatelessWidget {
3939
Size? size;
4040

4141
if (renderBox != null && renderBox is RenderBox) {
42-
position = renderBox.localToGlobal(Offset.zero);
43-
size = renderBox.size;
42+
position = renderBox.localToGlobal(Offset.zero);
43+
size = renderBox.size;
4444
}
4545

46-
final aiResult = await AiScribeModalManager.showAIScribeMenuModal(
46+
await AiScribeModalManager.showAIScribeMenuModal(
4747
imagePaths: imagePaths,
4848
availableCategories: AIScribeMenuCategory.values,
4949
buttonPosition: position,
5050
content: selectedText,
5151
buttonSize: size,
52+
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
5253
);
53-
54-
if (aiResult != null) {
55-
onAiScribeResultCallback(aiResult);
56-
}
5754
}
5855
}

scribe/lib/scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe
1818
import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_header.dart';
1919
import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart';
2020
import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart';
21-
22-
typedef OnAiScribeResultCallback = void Function(String result);
21+
import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart';
2322

2423
class AiScribeSuggestionWidget extends StatefulWidget {
2524
final AIAction aiAction;
@@ -29,11 +28,13 @@ class AiScribeSuggestionWidget extends StatefulWidget {
2928
final Size? buttonSize;
3029
final ModalPlacement? preferredPlacement;
3130
final ModalCrossAxisAlignment crossAxisAlignment;
31+
final OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction;
3232

3333
const AiScribeSuggestionWidget({
3434
super.key,
3535
required this.aiAction,
3636
required this.imagePaths,
37+
required this.onSelectAiScribeSuggestionAction,
3738
this.content,
3839
this.buttonPosition,
3940
this.buttonSize,
@@ -123,7 +124,7 @@ class _AiScribeSuggestionWidgetModalState
123124
return AiScribeSuggestionSuccess(
124125
imagePaths: widget.imagePaths,
125126
suggestionText: value.response.result,
126-
aiAction: widget.aiAction,
127+
onSelectAction: widget.onSelectAiScribeSuggestionAction,
127128
);
128129
} else if (value is GenerateAITextFailure) {
129130
return AiScribeSuggestionError(imagePaths: widget.imagePaths);

scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class AiScribeSuggestionLoading extends StatelessWidget {
2222
children: [
2323
_SparklePulseIcon(
2424
asset: imagePaths.icSparkle,
25-
color: AppColor.blueFF00B7FF,
25+
color: AppColor.blue00B7FF,
2626
),
2727
Expanded(
2828
child: _AnimatedEllipsisText(

scribe/lib/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import 'package:core/presentation/resources/image_paths.dart';
22
import 'package:flutter/material.dart';
3-
import 'package:scribe/scribe.dart';
43
import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart';
54
import 'package:scribe/scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart';
65

76
class AiScribeSuggestionSuccess extends StatelessWidget {
87
final ImagePaths imagePaths;
98
final String suggestionText;
10-
final AIAction aiAction;
9+
final OnSelectAiScribeSuggestionAction onSelectAction;
1110

1211
const AiScribeSuggestionSuccess({
1312
super.key,
1413
required this.imagePaths,
1514
required this.suggestionText,
16-
required this.aiAction,
15+
required this.onSelectAction,
1716
});
1817

1918
@override
@@ -23,6 +22,7 @@ class AiScribeSuggestionSuccess extends StatelessWidget {
2322
child: Column(
2423
spacing: 8,
2524
mainAxisSize: MainAxisSize.min,
25+
crossAxisAlignment: CrossAxisAlignment.start,
2626
children: [
2727
Flexible(
2828
child: Text(
@@ -33,7 +33,7 @@ class AiScribeSuggestionSuccess extends StatelessWidget {
3333
AiScribeSuggestionSuccessListActions(
3434
imagePaths: imagePaths,
3535
suggestionText: suggestionText,
36-
aiAction: aiAction,
36+
onSelectAction: onSelectAction,
3737
),
3838
],
3939
),
Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,70 @@
1-
import 'package:core/presentation/resources/image_paths.dart';
1+
import 'package:core/core.dart';
2+
import 'package:core/presentation/views/dialog/confirm_dialog_button.dart';
23
import 'package:flutter/material.dart';
3-
import 'package:scribe/scribe.dart';
4-
import 'package:scribe/scribe/ai/presentation/styles/ai_scribe_styles.dart';
4+
import 'package:scribe/scribe/ai/localizations/scribe_localizations.dart';
5+
import 'package:scribe/scribe/ai/presentation/model/context_menu/ai_scribe_suggestion_actions.dart';
6+
7+
typedef OnSelectAiScribeSuggestionAction = void Function(
8+
AiScribeSuggestionActions action,
9+
String suggestionText,
10+
);
511

612
class AiScribeSuggestionSuccessListActions extends StatelessWidget {
713
final ImagePaths imagePaths;
814
final String suggestionText;
9-
final AIAction aiAction;
15+
final OnSelectAiScribeSuggestionAction onSelectAction;
1016

1117
const AiScribeSuggestionSuccessListActions({
1218
super.key,
1319
required this.imagePaths,
1420
required this.suggestionText,
15-
required this.aiAction,
21+
required this.onSelectAction,
1622
});
1723

1824
@override
1925
Widget build(BuildContext context) {
20-
return Padding(
21-
padding: const EdgeInsetsDirectional.only(bottom: 8),
22-
child: Column(
23-
spacing: 8,
24-
mainAxisSize: MainAxisSize.min,
25-
children: [
26-
Flexible(
27-
child: Text(
28-
suggestionText,
29-
style: AIScribeTextStyles.suggestionContent,
26+
final localizations = ScribeLocalizations.of(context);
27+
28+
return Row(
29+
mainAxisAlignment: MainAxisAlignment.end,
30+
spacing: 8,
31+
children: [
32+
Flexible(
33+
child: Container(
34+
constraints: const BoxConstraints(minWidth: 67),
35+
height: 36,
36+
child: ConfirmDialogButton(
37+
label: AiScribeSuggestionActions.replace.getLabel(localizations),
38+
textColor: AppColor.primaryMain,
39+
onTapAction: () {
40+
Navigator.of(context).pop();
41+
onSelectAction(
42+
AiScribeSuggestionActions.replace,
43+
suggestionText,
44+
);
45+
},
46+
),
47+
),
48+
),
49+
Flexible(
50+
child: Container(
51+
constraints: const BoxConstraints(minWidth: 72),
52+
height: 36,
53+
child: ConfirmDialogButton(
54+
label: AiScribeSuggestionActions.insert.getLabel(localizations),
55+
backgroundColor: AppColor.blueD2E9FF,
56+
textColor: AppColor.primaryMain,
57+
onTapAction: () {
58+
Navigator.of(context).pop();
59+
onSelectAction(
60+
AiScribeSuggestionActions.insert,
61+
suggestionText,
62+
);
63+
},
3064
),
3165
),
32-
],
33-
),
66+
),
67+
],
3468
);
3569
}
3670
}

0 commit comments

Comments
 (0)