refactor: tweaks

This commit is contained in:
cogwheel0
2025-09-20 18:09:22 +05:30
parent 1fc1cf9739
commit 093e8f3c0d
3 changed files with 177 additions and 327 deletions

View File

@@ -934,19 +934,32 @@ class _ChatPageState extends ConsumerState<ChatPage> {
final conversationTitle = ref.watch(
activeConversationProvider.select((conv) => conv?.title),
);
final displayConversationTitle = (() {
final trimmed = conversationTitle?.trim();
if (trimmed != null && trimmed.isNotEmpty) {
return trimmed;
}
return l10n.newChat;
})();
final trimmedConversationTitle = conversationTitle?.trim();
final displayConversationTitle =
(trimmedConversationTitle != null &&
trimmedConversationTitle.isNotEmpty)
? trimmedConversationTitle
: null;
final formattedModelName = selectedModel != null
? _formatModelDisplayName(
selectedModel.name,
omitProvider: omitProviderInModelName,
)
: null;
final modelLabel = formattedModelName ?? l10n.chooseModel;
final hasConversationTitle = displayConversationTitle != null;
final TextStyle modelTextStyle = hasConversationTitle
? AppTypography.small.copyWith(
color: context.conduitTheme.textSecondary,
fontWeight: FontWeight.w600,
height: 1.2,
)
: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
fontSize: 18,
height: 1.3,
);
// Keyboard visibility
final keyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
@@ -1060,16 +1073,16 @@ class _ChatPageState extends ConsumerState<ChatPage> {
onPressed: _clearSelection,
)
: Builder(
builder: (ctx) => GestureDetector(
onTap: () {
// Open left drawer instead of bottom sheet
Scaffold.of(ctx).openDrawer();
},
child: Padding(
padding: const EdgeInsets.only(
left: Spacing.inputPadding,
),
child: Icon(
builder: (ctx) => Padding(
padding: const EdgeInsets.only(
left: Spacing.inputPadding,
),
child: IconButton(
onPressed: () {
// Open left drawer instead of bottom sheet
Scaffold.of(ctx).openDrawer();
},
icon: Icon(
Platform.isIOS
? CupertinoIcons.line_horizontal_3
: Icons.menu,
@@ -1087,155 +1100,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
fontWeight: FontWeight.w500,
),
)
: selectedModel != null
? GestureDetector(
onTap: () {
final modelsAsync = ref.read(modelsProvider);
modelsAsync.whenData(
(models) => _showModelDropdown(context, ref, models),
);
},
onLongPress: () {
final conversation = ref.read(activeConversationProvider);
if (conversation == null) return;
showConversationContextMenu(
context: context,
ref: ref,
conversation: conversation,
);
},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
MiddleEllipsisText(
displayConversationTitle,
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
fontSize: 18,
height: 1.3,
),
textAlign: TextAlign.center,
semanticsLabel: displayConversationTitle,
),
const SizedBox(height: Spacing.xs),
Transform.translate(
offset: const Offset(0, 0),
child: SizedBox(
height: 24,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Opacity(
opacity: 0.0,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.xs,
vertical: Spacing.xxs,
),
decoration: BoxDecoration(
color: context
.conduitTheme
.surfaceBackground
.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(
AppBorderRadius.badge,
),
border: Border.all(
color:
context.conduitTheme.dividerColor,
width: BorderWidth.thin,
),
),
child: Icon(
Platform.isIOS
? CupertinoIcons.chevron_down
: Icons.keyboard_arrow_down,
color: context.conduitTheme.iconSecondary,
size: IconSize.small,
),
),
),
const SizedBox(width: Spacing.xs),
Flexible(
child: MiddleEllipsisText(
formattedModelName!,
style: AppTypography.small.copyWith(
color: context.conduitTheme.textSecondary,
fontWeight: FontWeight.w600,
height: 1.2,
),
textAlign: TextAlign.center,
semanticsLabel: formattedModelName,
),
),
const SizedBox(width: Spacing.xs),
Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.xs,
vertical: Spacing.xxs,
),
decoration: BoxDecoration(
color: context
.conduitTheme
.surfaceBackground
.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(
AppBorderRadius.badge,
),
border: Border.all(
color: context.conduitTheme.dividerColor,
width: BorderWidth.thin,
),
),
child: Icon(
Platform.isIOS
? CupertinoIcons.chevron_down
: Icons.keyboard_arrow_down,
color: context.conduitTheme.iconSecondary,
size: IconSize.small,
),
),
],
),
),
),
if (isReviewerMode)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.sm,
vertical: 1.0,
),
decoration: BoxDecoration(
color: context.conduitTheme.success.withValues(
alpha: 0.1,
),
borderRadius: BorderRadius.circular(
AppBorderRadius.badge,
),
border: Border.all(
color: context.conduitTheme.success
.withValues(alpha: 0.3),
width: BorderWidth.thin,
),
),
child: Text(
'REVIEWER MODE',
style: AppTypography.captionStyle.copyWith(
color: context.conduitTheme.success,
fontWeight: FontWeight.w600,
fontSize: 9,
),
),
),
),
],
),
)
: GestureDetector(
onTap: () {
final modelsAsync = ref.read(modelsProvider);
@@ -1256,23 +1120,40 @@ class _ChatPageState extends ConsumerState<ChatPage> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
MiddleEllipsisText(
displayConversationTitle,
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
fontSize: 18,
height: 1.3,
),
textAlign: TextAlign.center,
semanticsLabel: displayConversationTitle,
AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
child: displayConversationTitle != null
? Column(
key: const ValueKey<bool>(true),
mainAxisSize: MainAxisSize.min,
children: [
MiddleEllipsisText(
displayConversationTitle,
style: AppTypography.headlineSmallStyle
.copyWith(
color: context
.conduitTheme
.textPrimary,
fontWeight: FontWeight.w600,
fontSize: 18,
height: 1.3,
),
textAlign: TextAlign.center,
semanticsLabel: displayConversationTitle,
),
const SizedBox(height: Spacing.xs),
],
)
: const SizedBox.shrink(
key: ValueKey<bool>(false),
),
),
const SizedBox(height: Spacing.xs),
Transform.translate(
offset: const Offset(0, 0),
child: SizedBox(
height: 24,
child: Row(
child: () {
final row = Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
@@ -1309,14 +1190,10 @@ class _ChatPageState extends ConsumerState<ChatPage> {
const SizedBox(width: Spacing.xs),
Flexible(
child: MiddleEllipsisText(
l10n.chooseModel,
style: AppTypography.small.copyWith(
color: context.conduitTheme.textSecondary,
fontWeight: FontWeight.w600,
height: 1.2,
),
modelLabel,
style: modelTextStyle,
textAlign: TextAlign.center,
semanticsLabel: l10n.chooseModel,
semanticsLabel: modelLabel,
),
),
const SizedBox(width: Spacing.xs),
@@ -1347,8 +1224,11 @@ class _ChatPageState extends ConsumerState<ChatPage> {
),
),
],
),
),
);
return hasConversationTitle
? SizedBox(height: 24, child: row)
: row;
}(),
),
if (isReviewerMode)
Padding(

View File

@@ -333,6 +333,9 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
final Brightness brightness = Theme.of(context).brightness;
final bool isActive = _focusNode.hasFocus || _hasText;
final Color composerSurface = context.conduitTheme.inputBackground;
final Color composerBackground = brightness == Brightness.dark
? composerSurface.withValues(alpha: 0.78)
: context.conduitTheme.surfaceContainerHighest;
final Color placeholderBase = context.conduitTheme.inputPlaceholder;
final Color placeholderFocused = context.conduitTheme.inputText.withValues(
alpha: 0.64,
@@ -428,9 +431,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
duration: const Duration(milliseconds: 180),
curve: Curves.easeOutCubic,
decoration: BoxDecoration(
color: brightness == Brightness.dark
? composerSurface.withValues(alpha: 0.78)
: composerSurface,
color: composerBackground,
borderRadius: BorderRadius.circular(_composerRadius),
border: Border.all(color: outlineColor, width: BorderWidth.thin),
boxShadow: [
@@ -462,9 +463,19 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(Spacing.sm),
padding: const EdgeInsets.fromLTRB(
Spacing.sm,
Spacing.xs,
Spacing.sm,
Spacing.xs,
),
child: Container(
padding: const EdgeInsets.all(Spacing.sm),
padding: const EdgeInsets.fromLTRB(
Spacing.sm,
Spacing.xs,
Spacing.sm,
Spacing.xs,
),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(_composerRadius),
@@ -555,6 +566,13 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
factor,
)!;
final FontWeight recordingWeight =
_isRecording
? FontWeight.w500
: FontWeight.w400;
final TextStyle baseChatStyle =
AppTypography.chatMessageStyle;
return TextField(
controller: _controller,
focusNode: _focusNode,
@@ -576,27 +594,20 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
),
keyboardAppearance: brightness,
cursorColor: animatedTextColor,
style: AppTypography.bodyLargeStyle
.copyWith(
color: animatedTextColor,
fontStyle: _isRecording
? FontStyle.italic
: FontStyle.normal,
fontWeight: _isRecording
? FontWeight.w500
: FontWeight.w400,
),
style: baseChatStyle.copyWith(
color: animatedTextColor,
fontStyle: _isRecording
? FontStyle.italic
: FontStyle.normal,
fontWeight: recordingWeight,
),
decoration: InputDecoration(
hintText: AppLocalizations.of(
context,
)!.messageHintText,
hintStyle: TextStyle(
hintStyle: baseChatStyle.copyWith(
color: animatedPlaceholder,
fontSize:
AppTypography.bodyLarge,
fontWeight: _isRecording
? FontWeight.w500
: FontWeight.w400,
fontWeight: recordingWeight,
fontStyle: _isRecording
? FontStyle.italic
: FontStyle.normal,
@@ -610,7 +621,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
contentPadding:
const EdgeInsets.symmetric(
horizontal: Spacing.sm,
vertical: Spacing.sm,
vertical: Spacing.xs,
),
isDense: true,
alignLabelWithHint: true,
@@ -647,6 +658,9 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
children: [
_buildOverflowButton(
tooltip: AppLocalizations.of(context)!.more,
webSearchActive: webSearchEnabled,
imageGenerationActive: imageGenEnabled,
toolsActive: selectedToolIds.isNotEmpty,
),
const SizedBox(width: Spacing.xs),
Expanded(
@@ -705,12 +719,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
return Container(
color: Colors.transparent,
padding: const EdgeInsets.only(
left: 0,
right: 0,
top: Spacing.xs,
bottom: 0,
),
padding: EdgeInsets.zero,
child: Column(mainAxisSize: MainAxisSize.min, children: [shell]),
);
}
@@ -778,14 +787,61 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
return result;
}
Widget _buildOverflowButton({required String tooltip}) {
final IconData icon = Platform.isIOS
? CupertinoIcons.ellipsis
: Icons.more_horiz;
return _buildRoundButton(
icon: icon,
onTap: widget.enabled && !_isRecording ? _showOverflowSheet : null,
tooltip: tooltip,
Widget _buildOverflowButton({
required String tooltip,
required bool webSearchActive,
required bool imageGenerationActive,
required bool toolsActive,
}) {
final bool enabled = widget.enabled && !_isRecording;
IconData icon;
Color? activeColor;
if (webSearchActive) {
icon = Platform.isIOS ? CupertinoIcons.search : Icons.search;
activeColor = context.conduitTheme.buttonPrimary;
} else if (imageGenerationActive) {
icon = Platform.isIOS ? CupertinoIcons.photo : Icons.image;
activeColor = context.conduitTheme.buttonPrimary;
} else if (toolsActive) {
icon = Platform.isIOS ? CupertinoIcons.wrench : Icons.build;
activeColor = context.conduitTheme.buttonPrimary;
} else {
icon = Platform.isIOS ? CupertinoIcons.add : Icons.add;
activeColor = null;
}
const double iconSize = IconSize.large;
final Color iconColor = !enabled
? context.conduitTheme.textPrimary.withValues(alpha: Alpha.disabled)
: (activeColor ??
context.conduitTheme.textPrimary.withValues(alpha: Alpha.strong));
return Tooltip(
message: tooltip,
child: Opacity(
opacity: enabled ? 1.0 : Alpha.disabled,
child: SizedBox(
width: TouchTarget.minimum,
height: TouchTarget.minimum,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(AppBorderRadius.round),
onTap: enabled
? () {
HapticFeedback.selectionClick();
_showOverflowSheet();
}
: null,
child: Center(
child: Icon(icon, size: iconSize, color: iconColor),
),
),
),
),
),
);
}
@@ -910,68 +966,6 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
);
}
Widget _buildRoundButton({
required IconData icon,
VoidCallback? onTap,
String? tooltip,
bool isActive = false,
}) {
const double buttonSize = TouchTarget.minimum;
final VoidCallback? callback = onTap;
final bool enabled = callback != null;
final Color borderColor = isActive
? context.conduitTheme.buttonPrimary
: context.conduitTheme.cardBorder.withValues(
alpha: enabled ? Alpha.medium : Alpha.disabled,
);
final Color fillColor = isActive
? context.conduitTheme.buttonPrimary.withValues(alpha: 0.18)
: context.conduitTheme.cardBackground;
final Color iconColor = enabled
? (isActive
? context.conduitTheme.buttonPrimaryText
: context.conduitTheme.textPrimary.withValues(
alpha: Alpha.strong,
))
: context.conduitTheme.textPrimary.withValues(alpha: Alpha.disabled);
return Tooltip(
message: tooltip ?? '',
child: Opacity(
opacity: enabled ? 1.0 : Alpha.disabled,
child: IgnorePointer(
ignoring: !enabled,
child: Material(
color: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.round),
side: BorderSide(color: borderColor, width: BorderWidth.thin),
),
child: InkWell(
borderRadius: BorderRadius.circular(AppBorderRadius.round),
onTap: onTap == null
? null
: () {
HapticFeedback.selectionClick();
onTap();
},
child: Container(
width: buttonSize,
height: buttonSize,
decoration: BoxDecoration(
color: fillColor,
borderRadius: BorderRadius.circular(AppBorderRadius.round),
boxShadow: ConduitShadows.button,
),
child: Icon(icon, size: IconSize.medium, color: iconColor),
),
),
),
),
),
);
}
Widget _buildPillButton({
required IconData icon,
required String label,