refactor: ux
This commit is contained in:
@@ -313,9 +313,11 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
),
|
||||
),
|
||||
|
||||
// Action buttons below the message content (always visible)
|
||||
const SizedBox(height: Spacing.sm),
|
||||
_buildActionButtons(),
|
||||
// Action buttons below the message content (only after streaming completes)
|
||||
if (!widget.isStreaming) ...[
|
||||
const SizedBox(height: Spacing.sm),
|
||||
_buildActionButtons(),
|
||||
],
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -387,15 +389,23 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
child: EnhancedImageAttachment(
|
||||
attachmentId: widget.message.attachmentIds![0],
|
||||
isMarkdownFormat: true,
|
||||
constraints: const BoxConstraints(maxWidth: 500, maxHeight: 400),
|
||||
disableAnimation: widget.isStreaming, // Disable animation during streaming
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500,
|
||||
maxHeight: 400,
|
||||
),
|
||||
disableAnimation:
|
||||
widget.isStreaming, // Disable animation during streaming
|
||||
),
|
||||
)
|
||||
: Wrap(
|
||||
key: ValueKey('multi_images_${widget.message.attachmentIds!.join('_')}'),
|
||||
key: ValueKey(
|
||||
'multi_images_${widget.message.attachmentIds!.join('_')}',
|
||||
),
|
||||
spacing: Spacing.sm,
|
||||
runSpacing: Spacing.sm,
|
||||
children: widget.message.attachmentIds!.map<Widget>((attachmentId) {
|
||||
children: widget.message.attachmentIds!.map<Widget>((
|
||||
attachmentId,
|
||||
) {
|
||||
return EnhancedImageAttachment(
|
||||
key: ValueKey('attachment_$attachmentId'),
|
||||
attachmentId: attachmentId,
|
||||
@@ -404,7 +414,8 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
maxWidth: imageCount == 2 ? 245 : 160,
|
||||
maxHeight: imageCount == 2 ? 245 : 160,
|
||||
),
|
||||
disableAnimation: widget.isStreaming, // Disable animation during streaming
|
||||
disableAnimation:
|
||||
widget.isStreaming, // Disable animation during streaming
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
@@ -415,7 +426,7 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
if (widget.message.files == null || widget.message.files!.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
|
||||
// Filter for image files
|
||||
final imageFiles = widget.message.files!
|
||||
.where((file) => file['type'] == 'image')
|
||||
@@ -439,24 +450,31 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
builder: (context) {
|
||||
final imageUrl = imageFiles[0]['url'] as String?;
|
||||
if (imageUrl == null) return const SizedBox.shrink();
|
||||
|
||||
|
||||
return EnhancedImageAttachment(
|
||||
attachmentId: imageUrl, // Pass URL directly as it handles URLs
|
||||
attachmentId:
|
||||
imageUrl, // Pass URL directly as it handles URLs
|
||||
isMarkdownFormat: true,
|
||||
constraints: const BoxConstraints(maxWidth: 500, maxHeight: 400),
|
||||
disableAnimation: widget.isStreaming, // Disable animation during streaming
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 500,
|
||||
maxHeight: 400,
|
||||
),
|
||||
disableAnimation: widget
|
||||
.isStreaming, // Disable animation during streaming
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Wrap(
|
||||
key: ValueKey('gen_multi_${imageFiles.map((f) => f['url']).join('_')}'),
|
||||
key: ValueKey(
|
||||
'gen_multi_${imageFiles.map((f) => f['url']).join('_')}',
|
||||
),
|
||||
spacing: Spacing.sm,
|
||||
runSpacing: Spacing.sm,
|
||||
children: imageFiles.map<Widget>((file) {
|
||||
final imageUrl = file['url'] as String?;
|
||||
if (imageUrl == null) return const SizedBox.shrink();
|
||||
|
||||
|
||||
return EnhancedImageAttachment(
|
||||
key: ValueKey('gen_attachment_$imageUrl'),
|
||||
attachmentId: imageUrl, // Pass URL directly
|
||||
@@ -465,7 +483,8 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
maxWidth: imageCount == 2 ? 245 : 160,
|
||||
maxHeight: imageCount == 2 ? 245 : 160,
|
||||
),
|
||||
disableAnimation: widget.isStreaming, // Disable animation during streaming
|
||||
disableAnimation:
|
||||
widget.isStreaming, // Disable animation during streaming
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
@@ -725,9 +725,8 @@ class FullScreenImageViewer extends ConsumerWidget {
|
||||
|
||||
await SharePlus.instance.share(ShareParams(files: [XFile(file.path)]));
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Failed to share image')));
|
||||
// Swallowing UI feedback per requirements; keep a log for debugging
|
||||
debugPrint('Failed to share image: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,36 +307,13 @@ class CopyOptionsSheet extends ConsumerWidget {
|
||||
if (content != null) {
|
||||
await Clipboard.setData(ClipboardData(text: content));
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'${messages.length} messages copied to clipboard',
|
||||
),
|
||||
backgroundColor: AppTheme.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to copy messages: ${result.error}'),
|
||||
backgroundColor: AppTheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error copying messages: $e'),
|
||||
backgroundColor: AppTheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -652,21 +629,10 @@ class MoreOptionsSheet extends ConsumerWidget {
|
||||
controller.clear();
|
||||
setState(() {}); // Refresh the dialog
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Tag "$tag" added')),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to add tag: $e'),
|
||||
backgroundColor: AppTheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -730,28 +696,10 @@ class MoreOptionsSheet extends ConsumerWidget {
|
||||
);
|
||||
setState(() {}); // Refresh the dialog
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Tag "$tag" removed'),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Failed to remove tag: $e',
|
||||
),
|
||||
backgroundColor:
|
||||
context.conduitTheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -818,23 +766,12 @@ class MoreOptionsSheet extends ConsumerWidget {
|
||||
ref.invalidate(archivedConversationsProvider);
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Conversation archived')),
|
||||
);
|
||||
|
||||
// Navigate back or clear current conversation
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to archive conversation: $e'),
|
||||
backgroundColor: AppTheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -848,7 +785,7 @@ class MoreOptionsSheet extends ConsumerWidget {
|
||||
confirmText: 'Delete',
|
||||
isDestructive: true,
|
||||
);
|
||||
|
||||
|
||||
if (confirmed == true && context.mounted) {
|
||||
_deleteMessages(context, ref);
|
||||
}
|
||||
@@ -880,23 +817,12 @@ class MoreOptionsSheet extends ConsumerWidget {
|
||||
ref.read(chatMessagesProvider.notifier).clearMessages();
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Conversation deleted')),
|
||||
);
|
||||
|
||||
// Navigate back to conversation list
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to delete conversation: $e'),
|
||||
backgroundColor: AppTheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +280,8 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
autofocus: false,
|
||||
maxLines: _isExpanded ? null : 1,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization:
|
||||
TextCapitalization.sentences,
|
||||
textInputAction: TextInputAction.newline,
|
||||
showCursor: true,
|
||||
cursorColor: context.conduitTheme.inputText,
|
||||
|
||||
@@ -213,20 +213,9 @@ class _TagManagementDialogState extends ConsumerState<TagManagementDialog> {
|
||||
ref.invalidate(conversationsProvider);
|
||||
_tagController.clear();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Tag "$tag" added')));
|
||||
}
|
||||
if (mounted) {}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error adding tag: $e'),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) {}
|
||||
} finally {
|
||||
setState(() => _isAdding = false);
|
||||
}
|
||||
@@ -240,20 +229,9 @@ class _TagManagementDialogState extends ConsumerState<TagManagementDialog> {
|
||||
await api.removeTagFromConversation(widget.conversation.id, tag);
|
||||
ref.invalidate(conversationsProvider);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Tag "$tag" removed')));
|
||||
}
|
||||
if (mounted) {}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error removing tag: $e'),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user