feat: 添加首页图片选择功能(拍照/相册)

This commit is contained in:
qzl
2026-03-11 17:20:35 +08:00
parent e20e7d2a02
commit 9f2b060282
8 changed files with 163 additions and 18 deletions
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/api/api_exception.dart';
import '../../../../core/di/injection.dart';
@@ -73,6 +74,7 @@ class _HomeScreenState extends State<HomeScreen>
bool _isRecording = false;
bool _isTranscribing = false;
int _unreadCount = 0;
final List<XFile> _selectedImages = [];
bool get _hasMessage => _messageController.text.trim().isNotEmpty;
@@ -154,6 +156,7 @@ class _HomeScreenState extends State<HomeScreen>
children: [
_buildHeader(context),
Expanded(child: _buildChatArea(context, state)),
_buildImagePreview(),
_buildInputContainer(context, state),
],
),
@@ -515,6 +518,71 @@ class _HomeScreenState extends State<HomeScreen>
return UiSchemaRenderer.render(item.uiCard);
}
Widget _buildImagePreview() {
if (_selectedImages.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.only(
left: _inputPadding,
right: _inputPadding,
bottom: AppSpacing.sm,
),
child: Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: _selectedImages.asMap().entries.map((entry) {
final index = entry.key;
final image = entry.value;
return _buildImageThumbnail(image, index);
}).toList(),
),
);
}
Widget _buildImageThumbnail(XFile image, int index) {
return Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(AppRadius.md),
child: Image.file(
File(image.path),
width: 80,
height: 80,
fit: BoxFit.cover,
),
),
Positioned(
top: 4,
right: 4,
child: GestureDetector(
onTap: () => _removeImage(index),
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: AppColors.red500,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.x,
size: 14,
color: AppColors.white,
),
),
),
),
],
);
}
void _removeImage(int index) {
setState(() {
_selectedImages.removeAt(index);
});
}
Widget _buildInputContainer(BuildContext context, ChatState state) {
final isWaitingAgent =
state.isWaitingFirstToken || state.isStreaming || state.isCancelling;
@@ -625,10 +693,17 @@ class _HomeScreenState extends State<HomeScreen>
Future<void> _sendMessage(BuildContext context) async {
final content = _messageController.text.trim();
if (content.isEmpty) return;
if (content.isEmpty && _selectedImages.isEmpty) return;
final images = List<XFile>.from(_selectedImages);
FocusScope.of(context).unfocus();
_messageController.clear();
await context.read<ChatBloc>().sendMessage(content);
setState(() {
_selectedImages.clear();
});
await context.read<ChatBloc>().sendMessage(content, images: images);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
@@ -814,7 +889,16 @@ class _HomeScreenState extends State<HomeScreen>
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) => const HomeSheet(),
builder: (context) => HomeSheet(
onImagesSelected: (images) {
setState(() {
final remaining = 3 - _selectedImages.length;
if (remaining > 0) {
_selectedImages.addAll(images.take(remaining));
}
});
},
),
);
}
}