feat(ui): Improve text overflow and spacing in chat drawer

This commit is contained in:
cogwheel0
2025-11-28 14:59:38 +05:30
parent e15ce5b7b6
commit 98ae65d08f
3 changed files with 220 additions and 198 deletions

View File

@@ -709,12 +709,13 @@ class _ChatPageState extends ConsumerState<ChatPage> {
?.cast<String, dynamic>();
final name =
meta?['name']?.toString() ?? parsed.host;
final collectionName =
result?['collection_name']?.toString();
final collectionName = result?['collection_name']
?.toString();
// Add as appropriate type
final notifier =
ref.read(contextAttachmentsProvider.notifier);
final notifier = ref.read(
contextAttachmentsProvider.notifier,
);
if (isYoutube) {
notifier.addYoutube(
displayName: name,
@@ -1680,28 +1681,29 @@ class _ChatPageState extends ConsumerState<ChatPage> {
constraints: BoxConstraints(
maxWidth: constraints.maxWidth,
),
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(
milliseconds: 250,
),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
child: displayConversationTitle != null
? Column(
key: ValueKey<String>(
displayConversationTitle,
),
mainAxisSize: MainAxisSize.min,
children: [
StreamingTitleText(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(
milliseconds: 250,
),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
child: displayConversationTitle != null
? Column(
key: ValueKey<String>(
displayConversationTitle,
),
mainAxisSize: MainAxisSize.min,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth:
constraints.maxWidth,
),
child: StreamingTitleText(
title:
displayConversationTitle,
style: AppTypography
@@ -1720,96 +1722,45 @@ class _ChatPageState extends ConsumerState<ChatPage> {
.textPrimary
.withValues(alpha: 0.8),
),
const SizedBox(
height: Spacing.xs,
),
],
)
: const SizedBox.shrink(
key: ValueKey<String>(
'empty-title',
),
const SizedBox(
height: Spacing.xs,
),
],
)
: const SizedBox.shrink(
key: ValueKey<String>(
'empty-title',
),
),
Transform.translate(
offset: const Offset(0, 0),
child: () {
const double iconPaddingX =
Spacing.xs;
const double iconPaddingY =
Spacing.xxs;
const double iconWidth =
IconSize.small;
const double iconBoxWidth =
(iconPaddingX * 2) +
(BorderWidth.thin * 2) +
iconWidth;
final double maxLabelWidth =
(constraints.maxWidth -
(iconBoxWidth * 2) -
(Spacing.xs * 2))
.clamp(
48.0,
constraints.maxWidth,
);
),
),
Transform.translate(
offset: const Offset(0, 0),
child: () {
const double iconPaddingX = Spacing.xs;
const double iconPaddingY = Spacing.xxs;
const double iconWidth = IconSize.small;
const double iconBoxWidth =
(iconPaddingX * 2) +
(BorderWidth.thin * 2) +
iconWidth;
final double maxLabelWidth =
(constraints.maxWidth -
(iconBoxWidth * 2) -
(Spacing.xs * 2))
.clamp(
48.0,
constraints.maxWidth,
);
final row = Row(
mainAxisAlignment:
MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Opacity(
opacity: 0.0,
child: Container(
padding:
const EdgeInsets.symmetric(
horizontal:
iconPaddingX,
vertical: iconPaddingY,
),
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: iconWidth,
),
),
),
const SizedBox(width: Spacing.xs),
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: maxLabelWidth,
),
child: MiddleEllipsisText(
modelLabel,
style: modelTextStyle,
textAlign: TextAlign.center,
semanticsLabel: modelLabel,
),
),
const SizedBox(width: Spacing.xs),
Container(
final row = Row(
mainAxisAlignment:
MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Opacity(
opacity: 0.0,
child: Container(
padding:
const EdgeInsets.symmetric(
horizontal: iconPaddingX,
@@ -1843,64 +1794,107 @@ class _ChatPageState extends ConsumerState<ChatPage> {
size: iconWidth,
),
),
],
);
final constrainedRow = ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth,
),
child: row,
);
return hasConversationTitle
? SizedBox(
height: 24,
child: constrainedRow,
)
: constrainedRow;
}(),
),
if (isReviewerMode)
Padding(
padding: const EdgeInsets.only(
top: 2.0,
const SizedBox(width: Spacing.xs),
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: maxLabelWidth,
),
child: MiddleEllipsisText(
modelLabel,
style: modelTextStyle,
textAlign: TextAlign.center,
semanticsLabel: modelLabel,
),
),
const SizedBox(width: Spacing.xs),
Container(
padding:
const EdgeInsets.symmetric(
horizontal: iconPaddingX,
vertical: iconPaddingY,
),
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: iconWidth,
),
),
],
);
final constrainedRow = ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth,
),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.sm,
vertical: 1.0,
child: row,
);
return hasConversationTitle
? SizedBox(
height: 24,
child: constrainedRow,
)
: constrainedRow;
}(),
),
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,
),
decoration: BoxDecoration(
border: Border.all(
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,
),
.withValues(alpha: 0.3),
width: BorderWidth.thin,
),
),
child: Text(
'REVIEWER MODE',
style: AppTypography.captionStyle
.copyWith(
color: context
.conduitTheme
.success,
fontWeight: FontWeight.w600,
fontSize: 9,
),
),
),
],
),
),
],
),
),
);

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import '../../../shared/widgets/middle_ellipsis_text.dart';
/// Displays a chat title that reveals characters with a streaming animation
/// whenever the title changes.
class StreamingTitleText extends StatefulWidget {
@@ -141,36 +143,53 @@ class _StreamingTitleTextState extends State<StreamingTitleText>
? widget.style.fontSize! * (widget.style.height ?? 1.1)
: 18.0);
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
// When the animation completes we fall back to the full string.
revealedGlyphs >= totalGlyphs ? _activeTitle : visibleText,
maxLines: 1,
overflow: TextOverflow.fade,
softWrap: false,
textAlign: TextAlign.center,
style: widget.style,
),
),
if (isAnimating)
FadeTransition(
opacity: _cursorOpacity,
child: Container(
width: widget.cursorWidth,
height: cursorHeight,
margin: const EdgeInsets.only(left: 2),
decoration: BoxDecoration(
color: cursorColor,
borderRadius: BorderRadius.circular(widget.cursorWidth),
),
// When animation is complete, use middle ellipsis for overflow.
// During animation, show partial text with standard Text widget.
final bool animationComplete = revealedGlyphs >= totalGlyphs;
// Use middle ellipsis when animation is complete
if (animationComplete) {
return MiddleEllipsisText(
_activeTitle,
style: widget.style,
textAlign: TextAlign.center,
semanticsLabel: _activeTitle,
);
}
// During animation, use IntrinsicWidth to size the row to the text,
// then clip any overflow from the cursor
return ClipRect(
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
visibleText,
maxLines: 1,
overflow: TextOverflow.clip,
softWrap: false,
textAlign: TextAlign.center,
style: widget.style,
),
),
],
if (isAnimating)
FadeTransition(
opacity: _cursorOpacity,
child: Container(
width: widget.cursorWidth,
height: cursorHeight,
margin: const EdgeInsets.only(left: 2),
decoration: BoxDecoration(
color: cursorColor,
borderRadius: BorderRadius.circular(widget.cursorWidth),
),
),
),
],
),
);
}