Fix Scribe on responsive mobile

- Button was not added in MobileResponsiveAppBar
- Mobile behavior related scripts were not correctly added for web composer
- Mobile behavior related scripts return value was not properly handled for web composer
- In responsive web, bottomsheet modal clicks were intercepted by iframe behind
This commit is contained in:
Théo Poizat
2026-02-10 08:27:30 +01:00
committed by Dat H. Pham
parent 46a12745a5
commit 03a03b9088
6 changed files with 99 additions and 54 deletions
+12 -3
View File
@@ -220,9 +220,12 @@ class HtmlUtils {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
window._savedRange = selection.getRangeAt(0).cloneRange();
return selection.toString();
const result = selection.toString()
window.parent.postMessage(JSON.stringify({ "type": "toDart: saveSelection", result }), "*");
return result;
}
delete window._savedRange;
window.parent.postMessage(JSON.stringify({ "type": "toDart: saveSelection", result: "" }), "*");
return "";
})();''',
name: 'saveSelection');
@@ -236,9 +239,12 @@ class HtmlUtils {
selection.removeAllRanges();
selection.addRange(window._savedRange);
delete window._savedRange;
return selection.toString();
const result = selection.toString()
window.parent.postMessage(JSON.stringify({ "type": "toDart: restoreSelection", result }), "*");
return result;
}
}
window.parent.postMessage(JSON.stringify({ "type": "toDart: restoreSelection", result: "" }), "*");
return "";
})();''',
name: 'restoreSelection');
@@ -247,8 +253,11 @@ class HtmlUtils {
script: '''
(() => {
if(window._savedRange) {
return window._savedRange.toString();
const result = window._savedRange.toString();
window.parent.postMessage(JSON.stringify({ "type": "toDart: getSavedSelection", result }), "*");
return result;
} else {
window.parent.postMessage(JSON.stringify({ "type": "toDart: getSavedSelection", result: "" }), "*");
return "";
}
})();''',
@@ -134,6 +134,9 @@ class ComposerView extends GetWidget<ComposerController> {
saveToDraftsAction: () => controller.handleClickSaveAsDraftsButton(context),
saveToTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
deleteComposerAction: controller.handleClickDeleteComposer,
onOpenAiAssistantModal: controller.isAIScribeAvailable
? controller.openAIAssistantModal
: null,
)),
ConstrainedBox(
constraints: BoxConstraints(
@@ -4,6 +4,7 @@ import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/platform_info.dart';
import 'package:custom_pop_up_menu/custom_pop_up_menu.dart';
import 'package:flutter/material.dart';
import 'package:scribe/scribe/ai/presentation/widgets/button/ai_assistant_button.dart';
import 'package:tmail_ui_user/features/base/widget/highlight_svg_icon_on_hover.dart';
import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu_overlay_widget.dart';
@@ -32,6 +33,7 @@ class MobileResponsiveAppBarComposerWidget extends StatelessWidget {
final VoidCallback saveToTemplateAction;
final VoidCallback deleteComposerAction;
final VoidCallback toggleMarkAsImportantAction;
final OnOpenAiAssistantModal? onOpenAiAssistantModal;
const MobileResponsiveAppBarComposerWidget({
super.key,
@@ -55,6 +57,7 @@ class MobileResponsiveAppBarComposerWidget extends StatelessWidget {
required this.saveToTemplateAction,
required this.deleteComposerAction,
required this.toggleMarkAsImportantAction,
this.onOpenAiAssistantModal,
});
@override
@@ -74,6 +77,14 @@ class MobileResponsiveAppBarComposerWidget extends StatelessWidget {
onTapActionCallback: onCloseViewAction
),
const Spacer(),
if (onOpenAiAssistantModal != null)
AiAssistantButton(
imagePaths: imagePaths,
margin: const EdgeInsetsDirectional.only(
end: MobileAppBarComposerWidgetStyle.space,
),
onOpenAiAssistantModal: onOpenAiAssistantModal!,
),
TMailButtonWidget.fromIcon(
icon: imagePaths.icRichToolbar,
padding: MobileAppBarComposerWidgetStyle.richTextIconPadding,
@@ -205,6 +205,22 @@ class _WebEditorState extends State<WebEditorWidget> with TextSelectionMixin {
name: HtmlUtils.deleteSelectionContent.name,
script: HtmlUtils.deleteSelectionContent.script,
),
WebScript(
name: HtmlUtils.saveSelection.name,
script: HtmlUtils.saveSelection.script,
),
WebScript(
name: HtmlUtils.restoreSelection.name,
script: HtmlUtils.restoreSelection.script,
),
WebScript(
name: HtmlUtils.getSavedSelection.name,
script: HtmlUtils.getSavedSelection.script,
),
WebScript(
name: HtmlUtils.clearSavedSelection.name,
script: HtmlUtils.clearSavedSelection.script,
),
WebScript(
name: HtmlUtils.recalculateEditorHeight(maxHeight: maxHeight).name,
script: HtmlUtils.recalculateEditorHeight(maxHeight: maxHeight).script,
@@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:scribe/scribe.dart';
class AiScribeMobileActionsBottomSheet extends StatefulWidget {
@@ -196,39 +197,41 @@ class _AiScribeMobileActionsBottomSheetState
final hasContent = widget.content?.isNotEmpty ?? false;
return Container(
height: double.infinity,
decoration: const BoxDecoration(
color: AIScribeColors.background,
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(context, localizations),
_buildTextCard(context),
if(hasContent)
Flexible(
child: ValueListenableBuilder<AiScribeCategoryContextMenuAction?>(
valueListenable: _selectedCategory,
builder: (context, selectedCategory, _) {
return selectedCategory == null
? _buildMenuListView(menuActions)
: _buildSubmenuListView();
},
return PointerInterceptor(
child: Container(
height: double.infinity,
decoration: const BoxDecoration(
color: AIScribeColors.background,
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(context, localizations),
_buildTextCard(context),
if(hasContent)
Flexible(
child: ValueListenableBuilder<AiScribeCategoryContextMenuAction?>(
valueListenable: _selectedCategory,
builder: (context, selectedCategory, _) {
return selectedCategory == null
? _buildMenuListView(menuActions)
: _buildSubmenuListView();
},
),
),
),
],
],
),
),
),
_buildBottomBar(context),
],
_buildBottomBar(context),
],
),
),
),
);
@@ -1,5 +1,6 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:scribe/scribe.dart';
class AiScribeMobileSuggestionBottomSheet extends StatefulWidget {
@@ -41,27 +42,29 @@ class _AiScribeMobileSuggestionBottomSheetState
Widget build(BuildContext context) {
final localizations = ScribeLocalizations.of(context);
return Container(
height: double.infinity,
decoration: const BoxDecoration(
color: AIScribeColors.background,
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: AIScribeSizes.suggestionHeaderPadding,
child: AiScribeSuggestionHeader(
title: aiAction.getLabel(localizations),
imagePaths: imagePaths,
return PointerInterceptor(
child: Container(
height: double.infinity,
decoration: const BoxDecoration(
color: AIScribeColors.background,
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: AIScribeSizes.suggestionHeaderPadding,
child: AiScribeSuggestionHeader(
title: aiAction.getLabel(localizations),
imagePaths: imagePaths,
),
),
),
Flexible(
child: buildStateContent(context),
),
],
Flexible(
child: buildStateContent(context),
),
],
),
),
),
);