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
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 {
@@ -131,6 +143,7 @@ abstract final class AIScribeSizes {
static const double aiAssistantIconRadius = 8;
static const double contentCardRadius = 16;
static const double improveButtonBorderRadius = 4;
static const double bottomBarRadius = 28;
// Width / height
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/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
@@ -154,33 +153,16 @@ class _AiScribeMobileActionsBottomSheetState
);
}
Widget _buildBottomBar(BuildContext context) {
Widget _buildBottomBarContent(BuildContext context) {
if (!widget.showCustomPromptBar) {
return const SizedBox.shrink();
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(
height: 1,
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 [],
),
),
],
return AIScribeBar(
onCustomPrompt: _onCustomPromptSubmit,
imagePaths: widget.imagePaths,
borderRadius: AIScribeSizes.bottomBarRadius,
hintStyle: AIScribeTextStyles.searchBar,
);
}
@@ -203,6 +185,9 @@ class _AiScribeMobileActionsBottomSheetState
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(
child: Container(
height: double.infinity,
@@ -210,32 +195,44 @@ class _AiScribeMobileActionsBottomSheetState
color: AIScribeColors.background,
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
child: Stack(
children: [
Expanded(
Padding(
padding: EdgeInsets.only(bottom: bottomBarInset),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
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();
},
),
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),
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 {
final OnCustomPromptCallback onCustomPrompt;
final ImagePaths imagePaths;
final List<BoxShadow>? boxShadow;
final double? borderRadius;
final TextStyle? hintStyle;
const AIScribeBar({
super.key,
required this.onCustomPrompt,
required this.imagePaths,
this.boxShadow,
this.borderRadius,
this.hintStyle,
});
@override
@@ -65,60 +67,68 @@ class _AIScribeBarState extends State<AIScribeBar> {
@override
Widget build(BuildContext context) {
final borderRadius = BorderRadius.circular(widget.borderRadius ?? AIScribeSizes.searchBarRadius);
return Container(
width: AIScribeSizes.searchBarWidth,
padding: AIScribeSizes.searchBarPadding,
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(AIScribeSizes.searchBarRadius),
),
color: AIScribeColors.background,
boxShadow: widget.boxShadow ?? AIScribeShadows.modal,
borderRadius: borderRadius,
gradient: AIScribeColors.barGradient,
),
constraints: const BoxConstraints(
maxHeight: AIScribeSizes.searchBarMaxHeight,
minHeight: AIScribeSizes.searchBarMinHeight,
),
child: Row(
children: [
Expanded(
child: KeyboardListener(
focusNode: _keyboardListenerFocusNode,
onKeyEvent: _handleKeyboardEvent,
child: TextField(
controller: _controller,
focusNode: _textFieldFocusNode,
decoration: InputDecoration(
hintText: ScribeLocalizations.of(context).customPromptAction,
hintStyle: AIScribeTextStyles.searchBarHint,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 8),
isDense: true,
child: ClipRRect(
borderRadius: borderRadius,
child: Container(
width: AIScribeSizes.searchBarWidth,
padding: AIScribeSizes.searchBarPadding,
decoration: const BoxDecoration(
color: AIScribeColors.background,
),
constraints: const BoxConstraints(
maxHeight: AIScribeSizes.searchBarMaxHeight,
minHeight: AIScribeSizes.searchBarMinHeight,
),
child: Row(
children: [
Expanded(
child: KeyboardListener(
focusNode: _keyboardListenerFocusNode,
onKeyEvent: _handleKeyboardEvent,
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,
);
},
),
],
),
),
);
}