Update Scribe mobile custom prompt bar UI
This commit is contained in:
@@ -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;
|
||||
|
||||
+39
-42
@@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user