From 093e8f3c0d8ddda29d0b1445f876280b24c10c6f Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Sat, 20 Sep 2025 18:09:22 +0530 Subject: [PATCH] refactor: tweaks --- lib/features/chat/views/chat_page.dart | 258 +++++------------- .../chat/widgets/modern_chat_input.dart | 190 +++++++------ .../navigation/widgets/chats_drawer.dart | 56 ++-- 3 files changed, 177 insertions(+), 327 deletions(-) diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 5d81034..a535ae2 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -934,19 +934,32 @@ class _ChatPageState extends ConsumerState { 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 { 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 { 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 { 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(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(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 { 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 { ), ), ], - ), - ), + ); + return hasConversationTitle + ? SizedBox(height: 24, child: row) + : row; + }(), ), if (isReviewerMode) Padding( diff --git a/lib/features/chat/widgets/modern_chat_input.dart b/lib/features/chat/widgets/modern_chat_input.dart index 3272573..4a8bfae 100644 --- a/lib/features/chat/widgets/modern_chat_input.dart +++ b/lib/features/chat/widgets/modern_chat_input.dart @@ -333,6 +333,9 @@ class _ModernChatInputState extends ConsumerState 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 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 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 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 ), 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 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 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 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 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 ); } - 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, diff --git a/lib/features/navigation/widgets/chats_drawer.dart b/lib/features/navigation/widgets/chats_drawer.dart index 91c7114..20b69d2 100644 --- a/lib/features/navigation/widgets/chats_drawer.dart +++ b/lib/features/navigation/widgets/chats_drawer.dart @@ -146,28 +146,7 @@ class _ChatsDrawerState extends ConsumerState { Spacing.md, Spacing.sm, ), - child: Row( - children: [ - Expanded(child: _buildSearchField(context)), - const SizedBox(width: Spacing.sm), - IconButton( - icon: Icon( - Platform.isIOS ? CupertinoIcons.create : Icons.add_comment, - color: theme.iconPrimary, - size: IconSize.lg, - ), - onPressed: () { - chat.startNewChat(ref); - if (mounted) Navigator.of(context).maybePop(); - }, - tooltip: AppLocalizations.of(context)!.newChat, - constraints: const BoxConstraints( - minWidth: TouchTarget.comfortable, - minHeight: TouchTarget.comfortable, - ), - ), - ], - ), + child: Row(children: [Expanded(child: _buildSearchField(context))]), ), Expanded(child: _buildConversationList(context)), Divider(height: 1, color: theme.dividerColor), @@ -1218,7 +1197,10 @@ class _ChatsDrawerState extends ConsumerState { if (user != null) ...[ const SizedBox(height: Spacing.sm), Container( - padding: const EdgeInsets.all(Spacing.sm), + padding: const EdgeInsets.symmetric( + horizontal: Spacing.sm, + vertical: Spacing.xs, + ), decoration: BoxDecoration( color: theme.surfaceContainer.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(AppBorderRadius.md), @@ -1231,8 +1213,8 @@ class _ChatsDrawerState extends ConsumerState { child: Row( children: [ Container( - width: IconSize.avatar, - height: IconSize.avatar, + width: IconSize.xl, + height: IconSize.xl, decoration: BoxDecoration( color: theme.buttonPrimary.withValues(alpha: 0.15), borderRadius: BorderRadius.circular( @@ -1252,7 +1234,7 @@ class _ChatsDrawerState extends ConsumerState { ), ), ), - const SizedBox(width: Spacing.sm), + const SizedBox(width: Spacing.xs), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1261,7 +1243,7 @@ class _ChatsDrawerState extends ConsumerState { displayName, maxLines: 1, overflow: TextOverflow.ellipsis, - style: AppTypography.standard.copyWith( + style: AppTypography.bodySmallStyle.copyWith( color: theme.textPrimary, fontWeight: FontWeight.w600, ), @@ -1314,14 +1296,13 @@ class _ConversationDragFeedback extends StatelessWidget { @override Widget build(BuildContext context) { - final borderColor = pinned - ? theme.navigationSelected.withValues(alpha: 0.35) - : theme.surfaceContainerHighest.withValues(alpha: 0.45); + final borderRadius = BorderRadius.circular(AppBorderRadius.navigation); + final borderColor = theme.surfaceContainerHighest.withValues(alpha: 0.40); return Material( color: Colors.transparent, elevation: Elevation.low, - borderRadius: BorderRadius.zero, + borderRadius: borderRadius, child: Container( constraints: const BoxConstraints(minHeight: TouchTarget.listItem), padding: const EdgeInsets.symmetric( @@ -1330,7 +1311,7 @@ class _ConversationDragFeedback extends StatelessWidget { ), decoration: BoxDecoration( color: theme.surfaceContainer, - borderRadius: BorderRadius.zero, + borderRadius: borderRadius, border: Border.all(color: borderColor, width: BorderWidth.thin), ), child: _ConversationTileContent( @@ -1472,14 +1453,9 @@ class _ConversationTile extends StatelessWidget { alpha: brightness == Brightness.dark ? 0.28 : 0.16, ) : theme.surfaceContainer; - final Color borderColor; - if (selected) { - borderColor = theme.buttonPrimary.withValues(alpha: 0.7); - } else if (pinned) { - borderColor = theme.buttonPrimary.withValues(alpha: 0.35); - } else { - borderColor = theme.surfaceContainerHighest.withValues(alpha: 0.40); - } + final Color borderColor = selected + ? theme.buttonPrimary.withValues(alpha: 0.7) + : theme.surfaceContainerHighest.withValues(alpha: 0.40); final List shadow = selected ? ConduitShadows.low : const []; Color? overlayForStates(Set states) {