From b36b89cd04591a78667fa5cc5263a77176ef036a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Poizat?= Date: Wed, 25 Feb 2026 09:54:21 +0100 Subject: [PATCH] Update Scribe mobile custom prompt bar UI --- .../presentation/styles/ai_scribe_styles.dart | 13 +++ ...ai_scribe_mobile_actions_bottom_sheet.dart | 81 +++++++------ .../widgets/search/ai_scribe_bar.dart | 110 ++++++++++-------- 3 files changed, 112 insertions(+), 92 deletions(-) diff --git a/scribe/lib/scribe/ai/presentation/styles/ai_scribe_styles.dart b/scribe/lib/scribe/ai/presentation/styles/ai_scribe_styles.dart index ed13d354e..5cc89a515 100644 --- a/scribe/lib/scribe/ai/presentation/styles/ai_scribe_styles.dart +++ b/scribe/lib/scribe/ai/presentation/styles/ai_scribe_styles.dart @@ -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; diff --git a/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart b/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart index d6d5886b7..c3b81effa 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart @@ -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( - 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( + 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), + ), ], ), ), diff --git a/scribe/lib/scribe/ai/presentation/widgets/search/ai_scribe_bar.dart b/scribe/lib/scribe/ai/presentation/widgets/search/ai_scribe_bar.dart index adf087406..5f3adb0f3 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/search/ai_scribe_bar.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/search/ai_scribe_bar.dart @@ -9,13 +9,15 @@ typedef OnCustomPromptCallback = void Function(String customPrompt); class AIScribeBar extends StatefulWidget { final OnCustomPromptCallback onCustomPrompt; final ImagePaths imagePaths; - final List? 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 { @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( + 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( - 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, - ); - }, - ), - ], + ), ), ); }