refactor: enhance chat page layout and improve drawer search functionality

This commit is contained in:
cogwheel0
2025-08-25 14:31:45 +05:30
parent eec2f7b8d7
commit d6f96ff5c5
2 changed files with 393 additions and 311 deletions

View File

@@ -1025,47 +1025,87 @@ class _ChatPageState extends ConsumerState<ChatPage> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Transform.translate(
mainAxisSize: MainAxisSize.min, offset: const Offset(-6, 0),
children: [ child: Center(
Flexible( child: Row(
child: Text( mainAxisSize: MainAxisSize.min,
_formatModelDisplayName(selectedModel.name), crossAxisAlignment: CrossAxisAlignment.center,
style: AppTypography.headlineSmallStyle children: [
.copyWith( Opacity(
color: context.conduitTheme.textPrimary, opacity: 0.0,
fontWeight: FontWeight.w400, child: Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.xs,
vertical: Spacing.xxs,
), ),
maxLines: 1, decoration: BoxDecoration(
overflow: TextOverflow.ellipsis, color: context
), .conduitTheme
), .surfaceBackground
const SizedBox(width: Spacing.xs), .withValues(alpha: 0.3),
Container( borderRadius: BorderRadius.circular(
padding: const EdgeInsets.symmetric( AppBorderRadius.badge,
horizontal: Spacing.xs, ),
vertical: Spacing.xxs, border: Border.all(
), color:
decoration: BoxDecoration( context.conduitTheme.dividerColor,
color: context.conduitTheme.surfaceBackground width: BorderWidth.thin,
.withValues(alpha: 0.3), ),
borderRadius: BorderRadius.circular( ),
AppBorderRadius.badge, child: Icon(
Platform.isIOS
? CupertinoIcons.chevron_down
: Icons.keyboard_arrow_down,
size: IconSize.small,
),
),
), ),
border: Border.all( const SizedBox(width: Spacing.xs),
color: context.conduitTheme.dividerColor, Flexible(
width: BorderWidth.thin, child: Text(
_formatModelDisplayName(selectedModel.name),
style: AppTypography.headlineSmallStyle
.copyWith(
color:
context.conduitTheme.textPrimary,
fontWeight: FontWeight.w400,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
), ),
), const SizedBox(width: Spacing.xs),
child: Icon( Container(
Platform.isIOS padding: const EdgeInsets.symmetric(
? CupertinoIcons.chevron_down horizontal: Spacing.xs,
: Icons.keyboard_arrow_down, vertical: Spacing.xxs,
color: context.conduitTheme.iconSecondary, ),
size: IconSize.small, 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 (ref.watch(reviewerModeProvider)) if (ref.watch(reviewerModeProvider))
Padding( Padding(
@@ -1111,49 +1151,87 @@ class _ChatPageState extends ConsumerState<ChatPage> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Transform.translate(
mainAxisSize: MainAxisSize.min, offset: const Offset(-6, 0),
mainAxisAlignment: MainAxisAlignment.center, child: Center(
children: [ child: Row(
Flexible( mainAxisSize: MainAxisSize.min,
child: Text( crossAxisAlignment: CrossAxisAlignment.center,
'Choose Model', children: [
style: AppTypography.headlineSmallStyle Opacity(
.copyWith( opacity: 0.0,
color: context.conduitTheme.textPrimary, child: Container(
fontWeight: FontWeight.w400, padding: const EdgeInsets.symmetric(
horizontal: Spacing.xs,
vertical: Spacing.xxs,
), ),
maxLines: 1, decoration: BoxDecoration(
overflow: TextOverflow.ellipsis, color: context
textAlign: TextAlign.center, .conduitTheme
), .surfaceBackground
), .withValues(alpha: 0.3),
const SizedBox(width: Spacing.xs), borderRadius: BorderRadius.circular(
Container( AppBorderRadius.badge,
padding: const EdgeInsets.symmetric( ),
horizontal: Spacing.xs, border: Border.all(
vertical: Spacing.xxs, color:
), context.conduitTheme.dividerColor,
decoration: BoxDecoration( width: BorderWidth.thin,
color: context.conduitTheme.surfaceBackground ),
.withValues(alpha: 0.3), ),
borderRadius: BorderRadius.circular( child: Icon(
AppBorderRadius.badge, Platform.isIOS
? CupertinoIcons.chevron_down
: Icons.keyboard_arrow_down,
size: IconSize.small,
),
),
), ),
border: Border.all( const SizedBox(width: Spacing.xs),
color: context.conduitTheme.dividerColor, Flexible(
width: BorderWidth.thin, child: Text(
'Choose Model',
style: AppTypography.headlineSmallStyle
.copyWith(
color:
context.conduitTheme.textPrimary,
fontWeight: FontWeight.w400,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
), ),
), const SizedBox(width: Spacing.xs),
child: Icon( Container(
Platform.isIOS padding: const EdgeInsets.symmetric(
? CupertinoIcons.chevron_down horizontal: Spacing.xs,
: Icons.keyboard_arrow_down, vertical: Spacing.xxs,
color: context.conduitTheme.iconSecondary, ),
size: IconSize.small, 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 (ref.watch(reviewerModeProvider)) if (ref.watch(reviewerModeProvider))
Padding( Padding(

View File

@@ -26,6 +26,7 @@ class ChatsDrawer extends ConsumerStatefulWidget {
class _ChatsDrawerState extends ConsumerState<ChatsDrawer> { class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
final FocusNode _searchFocusNode = FocusNode(debugLabel: 'drawer_search'); final FocusNode _searchFocusNode = FocusNode(debugLabel: 'drawer_search');
final ScrollController _listController = ScrollController();
Timer? _debounce; Timer? _debounce;
String _query = ''; String _query = '';
bool _isLoadingConversation = false; bool _isLoadingConversation = false;
@@ -44,6 +45,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
_debounce?.cancel(); _debounce?.cancel();
_searchController.dispose(); _searchController.dispose();
_searchFocusNode.dispose(); _searchFocusNode.dispose();
_listController.dispose();
super.dispose(); super.dispose();
} }
@@ -86,18 +88,23 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
? CupertinoIcons.bubble_left ? CupertinoIcons.bubble_left
: Icons.add_comment, : Icons.add_comment,
color: theme.iconPrimary, color: theme.iconPrimary,
size: IconSize.listItem,
), ),
onPressed: () { onPressed: () {
chat.startNewChat(ref); chat.startNewChat(ref);
if (mounted) Navigator.of(context).maybePop(); if (mounted) Navigator.of(context).maybePop();
}, },
tooltip: AppLocalizations.of(context)!.newChat, tooltip: AppLocalizations.of(context)!.newChat,
constraints: const BoxConstraints(
minWidth: TouchTarget.listItem,
minHeight: TouchTarget.listItem,
),
), ),
], ],
), ),
), ),
Expanded(child: _buildConversationList(context)), Expanded(child: _buildConversationList(context)),
const Divider(height: 1), Divider(height: 1, color: theme.dividerColor),
_buildBottomSection(context), _buildBottomSection(context),
], ],
), ),
@@ -106,64 +113,58 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
Widget _buildSearchField(BuildContext context) { Widget _buildSearchField(BuildContext context) {
final theme = context.conduitTheme; final theme = context.conduitTheme;
return Container( return TextField(
decoration: BoxDecoration( controller: _searchController,
gradient: LinearGradient( focusNode: _searchFocusNode,
colors: [ onChanged: (_) => _onSearchChanged(),
theme.inputBackground.withValues(alpha: 0.6), style: TextStyle(
theme.inputBackground.withValues(alpha: 0.3), color: theme.inputText,
], fontSize: AppTypography.bodyMedium,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
border: Border.all(
color: _searchFocusNode.hasFocus
? theme.buttonPrimary.withValues(alpha: 0.8)
: theme.inputBorder.withValues(alpha: 0.3),
width: BorderWidth.thin,
),
), ),
child: TextField( decoration: InputDecoration(
controller: _searchController, hintText: AppLocalizations.of(context)!.searchConversations,
focusNode: _searchFocusNode, hintStyle: TextStyle(
onChanged: (_) => _onSearchChanged(), color: theme.inputPlaceholder,
style: TextStyle(
color: theme.inputText,
fontSize: AppTypography.bodyMedium, fontSize: AppTypography.bodyMedium,
), ),
decoration: InputDecoration( prefixIcon: Icon(
hintText: AppLocalizations.of(context)!.searchConversations, Platform.isIOS ? CupertinoIcons.search : Icons.search,
hintStyle: TextStyle( color: theme.iconSecondary,
color: theme.inputPlaceholder.withValues(alpha: 0.8), size: IconSize.input,
fontSize: AppTypography.bodyMedium, ),
), suffixIcon: _query.isNotEmpty
prefixIcon: Icon( ? IconButton(
Platform.isIOS ? CupertinoIcons.search : Icons.search, onPressed: () {
color: theme.iconSecondary, _searchController.clear();
size: IconSize.md, setState(() => _query = '');
), _searchFocusNode.unfocus();
suffixIcon: _query.isNotEmpty },
? IconButton( icon: Icon(
onPressed: () { Platform.isIOS
_searchController.clear(); ? CupertinoIcons.clear_circled_solid
setState(() => _query = ''); : Icons.clear,
_searchFocusNode.unfocus(); color: theme.iconSecondary,
}, size: IconSize.input,
icon: Icon( ),
Platform.isIOS )
? CupertinoIcons.clear_circled_solid : null,
: Icons.clear, filled: true,
color: theme.iconSecondary, fillColor: theme.inputBackground,
size: IconSize.md, border: OutlineInputBorder(
), borderRadius: BorderRadius.circular(AppBorderRadius.md),
) borderSide: BorderSide.none,
: null, ),
border: InputBorder.none, enabledBorder: OutlineInputBorder(
contentPadding: const EdgeInsets.symmetric( borderRadius: BorderRadius.circular(AppBorderRadius.md),
horizontal: 16, borderSide: BorderSide(color: theme.inputBorder, width: 1),
vertical: 12, ),
), focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: BorderSide(color: theme.buttonPrimary, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.md,
), ),
), ),
); );
@@ -213,87 +214,93 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
.toList(); .toList();
final archived = list.where((c) => c.archived == true).toList(); final archived = list.where((c) => c.archived == true).toList();
return ListView( return Scrollbar(
padding: const EdgeInsets.fromLTRB( controller: _listController,
Spacing.md, child: ListView(
Spacing.sm, controller: _listController,
Spacing.md, padding: const EdgeInsets.fromLTRB(
Spacing.md, Spacing.md,
), Spacing.sm,
children: [ Spacing.md,
if (pinned.isNotEmpty) ...[ Spacing.md,
_buildSectionHeader( ),
AppLocalizations.of(context)!.pinned, children: [
pinned.length, if (pinned.isNotEmpty) ...[
), _buildSectionHeader(
const SizedBox(height: Spacing.xs), AppLocalizations.of(context)!.pinned,
...pinned.map((conv) => _buildTileFor(conv)), pinned.length,
const SizedBox(height: Spacing.md),
],
// Folders section (shown even if empty)
_buildFoldersSectionHeader(),
const SizedBox(height: Spacing.xs),
if (_isDragging && _draggingHasFolder) ...[
_buildUnfileDropTarget(),
const SizedBox(height: Spacing.sm),
],
...ref
.watch(foldersProvider)
.when(
data: (folders) {
final grouped = <String, List<dynamic>>{};
for (final c in foldered) {
final id = c.folderId!;
grouped.putIfAbsent(id, () => []).add(c);
}
// Show all folders (including empty)
final sections = folders.map((folder) {
final expandedMap = ref.watch(_expandedFoldersProvider);
final isExpanded = expandedMap[folder.id] ?? false;
final convs = grouped[folder.id] ?? const <dynamic>[];
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildFolderHeader(
folder.id,
folder.name,
convs.length,
),
if (isExpanded && convs.isNotEmpty) ...[
const SizedBox(height: Spacing.xs),
...convs.map(
(c) => _buildTileFor(c, inFolder: true),
),
const SizedBox(height: Spacing.sm),
],
],
);
}).toList();
return sections.isEmpty
? [const SizedBox.shrink()]
: sections;
},
loading: () => [const SizedBox.shrink()],
error: (e, st) => [const SizedBox.shrink()],
), ),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.xs),
...pinned.map((conv) => _buildTileFor(conv)),
const SizedBox(height: Spacing.md),
],
if (regular.isNotEmpty) ...[ // Folders section (shown even if empty)
_buildSectionHeader( _buildFoldersSectionHeader(),
AppLocalizations.of(context)!.recent,
regular.length,
),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...regular.map(_buildTileFor), if (_isDragging && _draggingHasFolder) ...[
], _buildUnfileDropTarget(),
const SizedBox(height: Spacing.sm),
],
...ref
.watch(foldersProvider)
.when(
data: (folders) {
final grouped = <String, List<dynamic>>{};
for (final c in foldered) {
final id = c.folderId!;
grouped.putIfAbsent(id, () => []).add(c);
}
if (archived.isNotEmpty) ...[ // Show all folders (including empty)
final sections = folders.map((folder) {
final expandedMap = ref.watch(
_expandedFoldersProvider,
);
final isExpanded = expandedMap[folder.id] ?? false;
final convs = grouped[folder.id] ?? const <dynamic>[];
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildFolderHeader(
folder.id,
folder.name,
convs.length,
),
if (isExpanded && convs.isNotEmpty) ...[
const SizedBox(height: Spacing.xs),
...convs.map(
(c) => _buildTileFor(c, inFolder: true),
),
const SizedBox(height: Spacing.sm),
],
],
);
}).toList();
return sections.isEmpty
? [const SizedBox.shrink()]
: sections;
},
loading: () => [const SizedBox.shrink()],
error: (e, st) => [const SizedBox.shrink()],
),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
_buildArchivedSection(archived),
if (regular.isNotEmpty) ...[
_buildSectionHeader(
AppLocalizations.of(context)!.recent,
regular.length,
),
const SizedBox(height: Spacing.xs),
...regular.map(_buildTileFor),
],
if (archived.isNotEmpty) ...[
const SizedBox(height: Spacing.md),
_buildArchivedSection(archived),
],
], ],
], ),
); );
}, },
loading: () => loading: () =>
@@ -350,85 +357,89 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
.toList(); .toList();
final archived = list.where((c) => c.archived == true).toList(); final archived = list.where((c) => c.archived == true).toList();
return ListView( return Scrollbar(
padding: const EdgeInsets.fromLTRB( controller: _listController,
Spacing.md, child: ListView(
Spacing.sm, controller: _listController,
Spacing.md, padding: const EdgeInsets.fromLTRB(
Spacing.md, Spacing.md,
), Spacing.sm,
children: [ Spacing.md,
_buildSectionHeader('Results', list.length), Spacing.md,
const SizedBox(height: Spacing.xs), ),
if (pinned.isNotEmpty) ...[ children: [
_buildSectionHeader( _buildSectionHeader('Results', list.length),
AppLocalizations.of(context)!.pinned,
pinned.length,
),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...pinned.map((conv) => _buildTileFor(conv)), if (pinned.isNotEmpty) ...[
const SizedBox(height: Spacing.md), _buildSectionHeader(
], AppLocalizations.of(context)!.pinned,
// Folders section (shown even if empty) pinned.length,
_buildFoldersSectionHeader(),
const SizedBox(height: Spacing.xs),
if (_isDragging && _draggingHasFolder) ...[
_buildUnfileDropTarget(),
const SizedBox(height: Spacing.sm),
],
...ref
.watch(foldersProvider)
.when(
data: (folders) {
final grouped = <String, List<dynamic>>{};
for (final c in foldered) {
final id = c.folderId!;
grouped.putIfAbsent(id, () => []).add(c);
}
final sections = folders.map((folder) {
final expandedMap = ref.watch(_expandedFoldersProvider);
final isExpanded = expandedMap[folder.id] ?? false;
final convs = grouped[folder.id] ?? const <dynamic>[];
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildFolderHeader(
folder.id,
folder.name,
convs.length,
),
if (isExpanded && convs.isNotEmpty) ...[
const SizedBox(height: Spacing.xs),
...convs.map(
(c) => _buildTileFor(c, inFolder: true),
),
const SizedBox(height: Spacing.sm),
],
],
);
}).toList();
return sections.isEmpty
? [const SizedBox.shrink()]
: sections;
},
loading: () => [const SizedBox.shrink()],
error: (e, st) => [const SizedBox.shrink()],
), ),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.xs),
if (regular.isNotEmpty) ...[ ...pinned.map((conv) => _buildTileFor(conv)),
_buildSectionHeader( const SizedBox(height: Spacing.md),
AppLocalizations.of(context)!.recent, ],
regular.length, // Folders section (shown even if empty)
), _buildFoldersSectionHeader(),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...regular.map(_buildTileFor), if (_isDragging && _draggingHasFolder) ...[
], _buildUnfileDropTarget(),
if (archived.isNotEmpty) ...[ const SizedBox(height: Spacing.sm),
],
...ref
.watch(foldersProvider)
.when(
data: (folders) {
final grouped = <String, List<dynamic>>{};
for (final c in foldered) {
final id = c.folderId!;
grouped.putIfAbsent(id, () => []).add(c);
}
final sections = folders.map((folder) {
final expandedMap = ref.watch(_expandedFoldersProvider);
final isExpanded = expandedMap[folder.id] ?? false;
final convs = grouped[folder.id] ?? const <dynamic>[];
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildFolderHeader(
folder.id,
folder.name,
convs.length,
),
if (isExpanded && convs.isNotEmpty) ...[
const SizedBox(height: Spacing.xs),
...convs.map(
(c) => _buildTileFor(c, inFolder: true),
),
const SizedBox(height: Spacing.sm),
],
],
);
}).toList();
return sections.isEmpty
? [const SizedBox.shrink()]
: sections;
},
loading: () => [const SizedBox.shrink()],
error: (e, st) => [const SizedBox.shrink()],
),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
_buildArchivedSection(archived), if (regular.isNotEmpty) ...[
_buildSectionHeader(
AppLocalizations.of(context)!.recent,
regular.length,
),
const SizedBox(height: Spacing.xs),
...regular.map(_buildTileFor),
],
if (archived.isNotEmpty) ...[
const SizedBox(height: Spacing.md),
_buildArchivedSection(archived),
],
], ],
], ),
); );
}, },
loading: () => loading: () =>
@@ -453,17 +464,13 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
children: [ children: [
Text( Text(
title, title,
style: AppTypography.bodySmallStyle.copyWith( style: AppTypography.labelStyle.copyWith(color: theme.textSecondary),
fontWeight: FontWeight.w600,
color: theme.textSecondary,
letterSpacing: 0.2,
),
), ),
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.surfaceBackground.withValues(alpha: 0.6), color: theme.surfaceContainer.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(AppBorderRadius.xs), borderRadius: BorderRadius.circular(AppBorderRadius.xs),
border: Border.all( border: Border.all(
color: theme.dividerColor, color: theme.dividerColor,
@@ -472,9 +479,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
), ),
child: Text( child: Text(
'$count', '$count',
style: AppTypography.bodySmallStyle.copyWith( style: AppTypography.tiny.copyWith(color: theme.textSecondary),
color: theme.textSecondary,
),
), ),
), ),
], ],
@@ -488,11 +493,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
children: [ children: [
Text( Text(
AppLocalizations.of(context)!.folders, AppLocalizations.of(context)!.folders,
style: AppTypography.bodySmallStyle.copyWith( style: AppTypography.labelStyle.copyWith(color: theme.textSecondary),
fontWeight: FontWeight.w600,
color: theme.textSecondary,
letterSpacing: 0.2,
),
), ),
const Spacer(), const Spacer(),
IconButton( IconButton(
@@ -615,7 +616,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
return Material( return Material(
color: isHover color: isHover
? theme.buttonPrimary.withValues(alpha: 0.08) ? theme.buttonPrimary.withValues(alpha: 0.08)
: theme.surfaceBackground.withValues(alpha: 0.05), : theme.surfaceContainer.withValues(alpha: 0.05),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
side: BorderSide( side: BorderSide(
@@ -652,6 +653,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
? CupertinoIcons.folder ? CupertinoIcons.folder
: Icons.folder), : Icons.folder),
color: theme.iconPrimary, color: theme.iconPrimary,
size: IconSize.listItem,
), ),
const SizedBox(width: Spacing.sm), const SizedBox(width: Spacing.sm),
Expanded( Expanded(
@@ -679,6 +681,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
? CupertinoIcons.chevron_down ? CupertinoIcons.chevron_down
: Icons.expand_more), : Icons.expand_more),
color: theme.iconSecondary, color: theme.iconSecondary,
size: IconSize.listItem,
), ),
], ],
), ),
@@ -890,7 +893,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: isHover color: isHover
? theme.buttonPrimary.withValues(alpha: 0.08) ? theme.buttonPrimary.withValues(alpha: 0.08)
: theme.surfaceBackground.withValues(alpha: 0.03), : theme.surfaceContainer.withValues(alpha: 0.03),
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
border: Border.all( border: Border.all(
color: isHover color: isHover
@@ -931,6 +934,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
Widget _buildTileFor(dynamic conv, {bool inFolder = false}) { Widget _buildTileFor(dynamic conv, {bool inFolder = false}) {
final isActive = ref.watch(activeConversationProvider)?.id == conv.id; final isActive = ref.watch(activeConversationProvider)?.id == conv.id;
final title = conv.title?.isEmpty == true ? 'Chat' : (conv.title ?? 'Chat'); final title = conv.title?.isEmpty == true ? 'Chat' : (conv.title ?? 'Chat');
final theme = context.conduitTheme;
final tile = _ConversationTile( final tile = _ConversationTile(
title: title, title: title,
pinned: conv.pinned == true, pinned: conv.pinned == true,
@@ -938,14 +942,12 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
onTap: _isLoadingConversation onTap: _isLoadingConversation
? null ? null
: () => _selectConversation(context, conv.id), : () => _selectConversation(context, conv.id),
// Remove long-press context menu to avoid conflict with drag gesture
onLongPress: null, onLongPress: null,
onMorePressed: () { onMorePressed: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
_showConversationContextMenu(context, conv); _showConversationContextMenu(context, conv);
}, },
); );
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: Spacing.xs, bottom: Spacing.xs,
@@ -966,12 +968,13 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
vertical: Spacing.sm, vertical: Spacing.sm,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).cardColor, color: theme.cardBackground,
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
border: Border.all( border: Border.all(
color: Theme.of(context).dividerColor, color: theme.dividerColor,
width: BorderWidth.regular, width: BorderWidth.regular,
), ),
boxShadow: ConduitShadows.card,
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -980,7 +983,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
Platform.isIOS Platform.isIOS
? CupertinoIcons.chat_bubble_2 ? CupertinoIcons.chat_bubble_2
: Icons.chat_bubble_outline, : Icons.chat_bubble_outline,
size: IconSize.md, size: IconSize.listItem,
), ),
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Text(title, maxLines: 1, overflow: TextOverflow.ellipsis), Text(title, maxLines: 1, overflow: TextOverflow.ellipsis),
@@ -1019,7 +1022,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Material( Material(
color: theme.surfaceBackground.withValues(alpha: 0.05), color: theme.surfaceContainer.withValues(alpha: 0.05),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
side: BorderSide( side: BorderSide(
@@ -1042,6 +1045,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
? CupertinoIcons.archivebox ? CupertinoIcons.archivebox
: Icons.archive_rounded, : Icons.archive_rounded,
color: theme.iconPrimary, color: theme.iconPrimary,
size: IconSize.listItem,
), ),
const SizedBox(width: Spacing.sm), const SizedBox(width: Spacing.sm),
Expanded( Expanded(
@@ -1069,6 +1073,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
? CupertinoIcons.chevron_down ? CupertinoIcons.chevron_down
: Icons.expand_more), : Icons.expand_more),
color: theme.iconSecondary, color: theme.iconSecondary,
size: IconSize.listItem,
), ),
], ],
), ),
@@ -1179,12 +1184,13 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
Container( Container(
padding: const EdgeInsets.all(Spacing.sm), padding: const EdgeInsets.all(Spacing.sm),
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.surfaceBackground.withValues(alpha: 0.04), color: theme.surfaceContainer.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
border: Border.all( border: Border.all(
color: theme.dividerColor, color: theme.dividerColor,
width: BorderWidth.regular, width: BorderWidth.regular,
), ),
boxShadow: ConduitShadows.card,
), ),
child: Row( child: Row(
children: [ children: [
@@ -1506,9 +1512,7 @@ class _ConversationTile extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.conduitTheme; final theme = context.conduitTheme;
return Material( return Material(
color: selected color: Colors.transparent,
? theme.buttonPrimary.withValues(alpha: 0.08)
: theme.surfaceBackground.withValues(alpha: 0.03),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
side: BorderSide( side: BorderSide(
@@ -1534,7 +1538,7 @@ class _ConversationTile extends StatelessWidget {
title, title,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: AppTypography.bodyLargeStyle.copyWith( style: AppTypography.standard.copyWith(
color: theme.textPrimary, color: theme.textPrimary,
fontWeight: selected ? FontWeight.w600 : FontWeight.w500, fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
), ),
@@ -1546,15 +1550,15 @@ class _ConversationTile extends StatelessWidget {
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints( constraints: const BoxConstraints(
minWidth: 36, minWidth: TouchTarget.listItem,
minHeight: 36, minHeight: TouchTarget.listItem,
), ),
icon: Icon( icon: Icon(
Platform.isIOS Platform.isIOS
? CupertinoIcons.ellipsis ? CupertinoIcons.ellipsis
: Icons.more_vert_rounded, : Icons.more_vert_rounded,
color: theme.iconSecondary, color: theme.iconSecondary,
size: IconSize.md, size: IconSize.listItem,
), ),
onPressed: onMorePressed, onPressed: onMorePressed,
tooltip: AppLocalizations.of(context)!.more, tooltip: AppLocalizations.of(context)!.more,