feat(ai-scribe): Improve UI for AI Scribe suggestion modal

This commit is contained in:
dab246
2025-12-18 03:40:53 +07:00
committed by Dat H. Pham
parent 582b1ebe97
commit 1a1b8f489f
11 changed files with 125 additions and 55 deletions
@@ -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);
@@ -22,7 +22,7 @@ class AiScribeSuggestionLoading extends StatelessWidget {
children: [
_SparklePulseIcon(
asset: imagePaths.icSparkle,
color: AppColor.blueFF00B7FF,
color: AppColor.blue00B7FF,
),
Expanded(
child: _AnimatedEllipsisText(
@@ -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,
),
],
),
@@ -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,
),
),
);