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