Update Scribe mobile custom prompt bar UI

This commit is contained in:
Théo Poizat
2026-02-25 09:54:21 +01:00
committed by Dat H. Pham
parent 1e43ac510b
commit b36b89cd04
3 changed files with 112 additions and 92 deletions
@@ -16,6 +16,18 @@ abstract final class AIScribeColors {
// Overlays // Overlays
static final Color dialogBarrier = Colors.black.withValues(alpha: 0.12); static final Color dialogBarrier = Colors.black.withValues(alpha: 0.12);
// Gradients
static const LinearGradient barGradient = LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromRGBO(0, 183, 255, 0.90),
Color.fromRGBO(224, 109, 209, 0.90),
Color.fromRGBO(232, 167, 138, 0.90),
],
stops: [0.0, 0.75, 1.0],
);
} }
abstract final class AIScribeShadows { abstract final class AIScribeShadows {
@@ -131,6 +143,7 @@ abstract final class AIScribeSizes {
static const double aiAssistantIconRadius = 8; static const double aiAssistantIconRadius = 8;
static const double contentCardRadius = 16; static const double contentCardRadius = 16;
static const double improveButtonBorderRadius = 4; static const double improveButtonBorderRadius = 4;
static const double bottomBarRadius = 28;
// Width / height // Width / height
static const double menuItemHeight = 40; static const double menuItemHeight = 40;
@@ -1,4 +1,3 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -154,33 +153,16 @@ class _AiScribeMobileActionsBottomSheetState
); );
} }
Widget _buildBottomBar(BuildContext context) { Widget _buildBottomBarContent(BuildContext context) {
if (!widget.showCustomPromptBar) { if (!widget.showCustomPromptBar) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Column( return AIScribeBar(
mainAxisSize: MainAxisSize.min, onCustomPrompt: _onCustomPromptSubmit,
children: [ imagePaths: widget.imagePaths,
const Divider( borderRadius: AIScribeSizes.bottomBarRadius,
height: 1, hintStyle: AIScribeTextStyles.searchBar,
thickness: 1,
color: AppColor.colorDividerComposer,
),
Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + 8.0,
left: 16.0,
right: 16.0,
top: 8.0,
),
child: AIScribeBar(
onCustomPrompt: _onCustomPromptSubmit,
imagePaths: widget.imagePaths,
boxShadow: const [],
),
),
],
); );
} }
@@ -203,6 +185,9 @@ class _AiScribeMobileActionsBottomSheetState
final hasContent = widget.content?.isNotEmpty ?? false; final hasContent = widget.content?.isNotEmpty ?? false;
final bottomBarPadding = MediaQuery.of(context).viewInsets.bottom + 16.0;
final bottomBarInset = bottomBarPadding + (widget.showCustomPromptBar ? AIScribeSizes.searchBarMaxHeight : 0.0);
return PointerInterceptor( return PointerInterceptor(
child: Container( child: Container(
height: double.infinity, height: double.infinity,
@@ -210,32 +195,44 @@ class _AiScribeMobileActionsBottomSheetState
color: AIScribeColors.background, color: AIScribeColors.background,
), ),
child: SafeArea( child: SafeArea(
child: Column( child: Stack(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Expanded( Padding(
padding: EdgeInsets.only(bottom: bottomBarInset),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildHeader(context, localizations), Expanded(
_buildTextCard(context), child: Column(
if(hasContent) mainAxisSize: MainAxisSize.min,
Flexible( crossAxisAlignment: CrossAxisAlignment.stretch,
child: ValueListenableBuilder<AiScribeCategoryContextMenuAction?>( children: [
valueListenable: _selectedCategory, _buildHeader(context, localizations),
builder: (context, selectedCategory, _) { _buildTextCard(context),
return selectedCategory == null if(hasContent)
? _buildMenuListView(menuActions) Flexible(
: _buildSubmenuListView(); child: ValueListenableBuilder<AiScribeCategoryContextMenuAction?>(
}, valueListenable: _selectedCategory,
), builder: (context, selectedCategory, _) {
return selectedCategory == null
? _buildMenuListView(menuActions)
: _buildSubmenuListView();
},
),
),
],
), ),
),
], ],
), ),
), ),
_buildBottomBar(context), Positioned(
bottom: bottomBarPadding,
left: 16.0,
right: 16.0,
child: _buildBottomBarContent(context),
),
], ],
), ),
), ),
@@ -9,13 +9,15 @@ typedef OnCustomPromptCallback = void Function(String customPrompt);
class AIScribeBar extends StatefulWidget { class AIScribeBar extends StatefulWidget {
final OnCustomPromptCallback onCustomPrompt; final OnCustomPromptCallback onCustomPrompt;
final ImagePaths imagePaths; final ImagePaths imagePaths;
final List<BoxShadow>? boxShadow; final double? borderRadius;
final TextStyle? hintStyle;
const AIScribeBar({ const AIScribeBar({
super.key, super.key,
required this.onCustomPrompt, required this.onCustomPrompt,
required this.imagePaths, required this.imagePaths,
this.boxShadow, this.borderRadius,
this.hintStyle,
}); });
@override @override
@@ -65,60 +67,68 @@ class _AIScribeBarState extends State<AIScribeBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final borderRadius = BorderRadius.circular(widget.borderRadius ?? AIScribeSizes.searchBarRadius);
return Container( return Container(
width: AIScribeSizes.searchBarWidth, padding: const EdgeInsets.all(2),
padding: AIScribeSizes.searchBarPadding,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.all( borderRadius: borderRadius,
Radius.circular(AIScribeSizes.searchBarRadius), gradient: AIScribeColors.barGradient,
),
color: AIScribeColors.background,
boxShadow: widget.boxShadow ?? AIScribeShadows.modal,
), ),
constraints: const BoxConstraints( child: ClipRRect(
maxHeight: AIScribeSizes.searchBarMaxHeight, borderRadius: borderRadius,
minHeight: AIScribeSizes.searchBarMinHeight, child: Container(
), width: AIScribeSizes.searchBarWidth,
child: Row( padding: AIScribeSizes.searchBarPadding,
children: [ decoration: const BoxDecoration(
Expanded( color: AIScribeColors.background,
child: KeyboardListener( ),
focusNode: _keyboardListenerFocusNode, constraints: const BoxConstraints(
onKeyEvent: _handleKeyboardEvent, maxHeight: AIScribeSizes.searchBarMaxHeight,
child: TextField( minHeight: AIScribeSizes.searchBarMinHeight,
controller: _controller, ),
focusNode: _textFieldFocusNode, child: Row(
decoration: InputDecoration( children: [
hintText: ScribeLocalizations.of(context).customPromptAction, Expanded(
hintStyle: AIScribeTextStyles.searchBarHint, child: KeyboardListener(
border: InputBorder.none, focusNode: _keyboardListenerFocusNode,
contentPadding: const EdgeInsets.symmetric(vertical: 8), onKeyEvent: _handleKeyboardEvent,
isDense: true, child: TextField(
controller: _controller,
focusNode: _textFieldFocusNode,
decoration: InputDecoration(
hintText: ScribeLocalizations.of(context).customPromptAction,
hintStyle: widget.hintStyle ?? AIScribeTextStyles.searchBarHint,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 8),
isDense: true,
),
style: AIScribeTextStyles.searchBar,
maxLines: null,
keyboardType: TextInputType.multiline,
cursorHeight: 16,
),
), ),
style: AIScribeTextStyles.searchBar,
maxLines: null,
keyboardType: TextInputType.multiline,
cursorHeight: 16,
), ),
), const SizedBox(width: AIScribeSizes.fieldSpacing),
ValueListenableBuilder<bool>(
valueListenable: _isButtonEnabled,
builder: (_, isEnabled, __) {
return TMailButtonWidget.fromIcon(
icon: widget.imagePaths.icSend,
iconSize: AIScribeSizes.sendIcon,
padding: AIScribeSizes.sendIconPadding,
iconColor: AIScribeColors.background,
backgroundColor: isEnabled
? AIScribeColors.sendPromptBackground
: AIScribeColors.sendPromptBackgroundDisabled,
onTapActionCallback: isEnabled ? _onSendPressed : null,
);
},
),
],
), ),
const SizedBox(width: AIScribeSizes.fieldSpacing), ),
ValueListenableBuilder<bool>(
valueListenable: _isButtonEnabled,
builder: (_, isEnabled, __) {
return TMailButtonWidget.fromIcon(
icon: widget.imagePaths.icSend,
iconSize: AIScribeSizes.sendIcon,
padding: AIScribeSizes.sendIconPadding,
iconColor: AIScribeColors.background,
backgroundColor: isEnabled
? AIScribeColors.sendPromptBackground
: AIScribeColors.sendPromptBackgroundDisabled,
onTapActionCallback: isEnabled ? _onSendPressed : null,
);
},
),
],
), ),
); );
} }