add dleete image projects

This commit is contained in:
Phuoc Nguyen
2025-11-28 13:47:47 +07:00
parent 6e7e848ad6
commit ed6cc4cebc
6 changed files with 300 additions and 39 deletions

View File

@@ -51,6 +51,7 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
List<ProjectFile> _existingFiles = []; // Existing files from API
bool _isSubmitting = false;
bool _isLoadingDetail = false;
String? _deletingFileId; // Track which file is being deleted
/// Whether we're editing an existing submission
bool get isEditing => widget.submission != null;
@@ -858,49 +859,76 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
Widget _buildExistingFileItem(ProjectFile file, int index) {
final fileName = file.fileUrl.split('/').last;
final isDeleting = _deletingFileId == file.id;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF8F9FA),
border: Border.all(color: AppColors.success),
border: Border.all(
color: isDeleting ? AppColors.grey500 : AppColors.success,
),
borderRadius: BorderRadius.circular(6),
),
child: Row(
children: [
// Network image
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: CachedNetworkImage(
imageUrl: file.fileUrl,
width: 48,
height: 48,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
width: 48,
height: 48,
color: AppColors.grey100,
child: const Center(
child: SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
// Network image with delete overlay
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: CachedNetworkImage(
imageUrl: file.fileUrl,
width: 48,
height: 48,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
width: 48,
height: 48,
color: AppColors.grey100,
child: const Center(
child: SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
),
errorWidget: (context, url, error) => Container(
width: 48,
height: 48,
color: AppColors.grey100,
child: const Center(
child: FaIcon(
FontAwesomeIcons.image,
size: 24,
color: AppColors.grey500,
),
),
),
),
),
errorWidget: (context, url, error) => Container(
width: 48,
height: 48,
color: AppColors.grey100,
child: const Center(
child: FaIcon(
FontAwesomeIcons.image,
size: 24,
color: AppColors.grey500,
// Deleting overlay
if (isDeleting)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(4),
),
child: const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
),
),
),
),
),
],
),
const SizedBox(width: 12),
Expanded(
@@ -909,33 +937,185 @@ class _SubmissionCreatePageState extends ConsumerState<SubmissionCreatePage> {
children: [
Text(
fileName,
style: const TextStyle(
style: TextStyle(
fontWeight: FontWeight.w500,
color: AppColors.grey900,
color: isDeleting ? AppColors.grey500 : AppColors.grey900,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
const Text(
'Đã tải lên',
Text(
isDeleting ? 'Đang xóa...' : 'Đã tải lên',
style: TextStyle(
fontSize: 12,
color: AppColors.success,
color: isDeleting ? AppColors.grey500 : AppColors.success,
),
),
],
),
),
// Checkmark icon
const FaIcon(
FontAwesomeIcons.circleCheck,
size: 16,
color: AppColors.success,
// Delete button or checkmark
if (!_isSubmitting && !isDeleting)
IconButton(
icon: const FaIcon(
FontAwesomeIcons.trash,
size: 16,
color: AppColors.danger,
),
onPressed: () => _showDeleteConfirmDialog(file),
tooltip: 'Xóa ảnh',
)
else if (!isDeleting)
const FaIcon(
FontAwesomeIcons.circleCheck,
size: 16,
color: AppColors.success,
),
],
),
);
}
/// Show confirmation dialog before deleting an existing file
Future<void> _showDeleteConfirmDialog(ProjectFile file) async {
final fileName = file.fileUrl.split('/').last;
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Row(
children: [
FaIcon(
FontAwesomeIcons.triangleExclamation,
color: AppColors.danger,
size: 20,
),
SizedBox(width: 12),
Text('Xác nhận xóa'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Bạn có chắc chắn muốn xóa ảnh này?'),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.grey100,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: CachedNetworkImage(
imageUrl: file.fileUrl,
width: 48,
height: 48,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
width: 48,
height: 48,
color: AppColors.grey100,
),
errorWidget: (context, url, error) => Container(
width: 48,
height: 48,
color: AppColors.grey100,
child: const Center(child: FaIcon(FontAwesomeIcons.image, size: 20)),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
fileName,
style: const TextStyle(
fontSize: 13,
color: AppColors.grey500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(height: 12),
const Text(
'Hành động này không thể hoàn tác.',
style: TextStyle(
fontSize: 13,
color: AppColors.danger,
fontStyle: FontStyle.italic,
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Hủy'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.danger,
foregroundColor: Colors.white,
),
child: const Text('Xóa'),
),
],
),
);
if (confirmed == true && mounted) {
await _deleteExistingFile(file);
}
}
/// Delete an existing file from the project
Future<void> _deleteExistingFile(ProjectFile file) async {
if (!isEditing || widget.submission == null) return;
setState(() => _deletingFileId = file.id);
try {
final repository = await ref.read(submissionsRepositoryProvider.future);
await repository.deleteProjectFile(
fileId: file.id,
projectName: widget.submission!.submissionId,
);
if (!mounted) return;
// Remove from local list
setState(() {
_existingFiles = _existingFiles.where((f) => f.id != file.id).toList();
_deletingFileId = null;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Đã xóa ảnh thành công'),
backgroundColor: AppColors.success,
),
);
} catch (e) {
if (mounted) {
setState(() => _deletingFileId = null);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Lỗi xóa ảnh: ${e.toString().replaceAll('Exception: ', '')}'),
backgroundColor: AppColors.danger,
),
);
}
}
}
Widget _buildSubmitButton() {