delete image working

This commit is contained in:
gitea
2025-11-06 23:52:41 +01:00
parent 2c5f0eaefc
commit 84f1712916
4 changed files with 315 additions and 54 deletions
+171 -44
View File
@@ -30,6 +30,8 @@ class _ImmichScreenState extends State<ImmichScreen> {
String? _errorMessage;
final ImagePicker _imagePicker = ImagePicker();
bool _isUploading = false;
Set<String> _selectedAssetIds = {}; // Track selected assets for deletion
bool _isSelectionMode = false;
@override
void initState() {
@@ -102,24 +104,40 @@ class _ImmichScreenState extends State<ImmichScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Immich Media'),
title: Text(_isSelectionMode
? '${_selectedAssetIds.length} selected'
: 'Immich Media'),
actions: [
IconButton(
icon: const Icon(Icons.photo_library),
onPressed: _pickAndUploadImages,
tooltip: 'Upload from Gallery',
),
IconButton(
icon: const Icon(Icons.info_outline),
onPressed: _testServerConnection,
tooltip: 'Test Server Connection',
),
if (_assets.isNotEmpty)
if (_isSelectionMode) ...[
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _loadAssets(forceRefresh: true),
tooltip: 'Refresh',
icon: const Icon(Icons.delete),
onPressed: _selectedAssetIds.isEmpty ? null : _deleteSelectedAssets,
tooltip: 'Delete Selected',
color: Colors.red,
),
IconButton(
icon: const Icon(Icons.close),
onPressed: _exitSelectionMode,
tooltip: 'Cancel Selection',
),
] else ...[
IconButton(
icon: const Icon(Icons.photo_library),
onPressed: _pickAndUploadImages,
tooltip: 'Upload from Gallery',
),
IconButton(
icon: const Icon(Icons.info_outline),
onPressed: _testServerConnection,
tooltip: 'Test Server Connection',
),
if (_assets.isNotEmpty)
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _loadAssets(forceRefresh: true),
tooltip: 'Refresh',
),
],
],
),
body: _buildBody(),
@@ -219,37 +237,66 @@ class _ImmichScreenState extends State<ImmichScreen> {
Widget _buildImageTile(ImmichAsset asset) {
final thumbnailUrl = _getThumbnailUrl(asset);
// For Immich API, we need to pass the API key as a header
// Since Image.network doesn't easily support custom headers,
// we'll use a workaround: Immich might accept the key in the URL query parameter
// OR we need to fetch images via Dio and convert to bytes.
// Let's check ImmichService - it has Dio with headers configured.
// Actually, we can use Image.network with headers parameter (Flutter supports this).
// But we need the API key. Let me check if ImmichService exposes it.
// Since Immich API requires x-api-key header, and Image.network supports headers,
// we need to get the API key. However, ImmichService doesn't expose it.
// Let's modify ImmichService to expose a method that returns headers, or
// we can fetch images via Dio and display them.
// For now, let's use Image.network and assume Immich might work without header
// (which it won't, but this is a placeholder). We'll fix this properly next.
final isSelected = _selectedAssetIds.contains(asset.id);
return GestureDetector(
onTap: () {
// TODO: Navigate to full image view
_showImageDetails(asset);
onLongPress: () {
if (!_isSelectionMode) {
setState(() {
_isSelectionMode = true;
_selectedAssetIds.add(asset.id);
});
}
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.grey[300],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: _buildImageWidget(thumbnailUrl, asset),
),
onTap: () {
if (_isSelectionMode) {
setState(() {
if (isSelected) {
_selectedAssetIds.remove(asset.id);
if (_selectedAssetIds.isEmpty) {
_isSelectionMode = false;
}
} else {
_selectedAssetIds.add(asset.id);
}
});
} else {
_showImageDetails(asset);
}
},
child: Stack(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.grey[300],
border: isSelected
? Border.all(color: Colors.blue, width: 3)
: null,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: _buildImageWidget(thumbnailUrl, asset),
),
),
if (isSelected)
Positioned(
top: 8,
right: 8,
child: Container(
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
padding: const EdgeInsets.all(4),
child: const Icon(
Icons.check_circle,
color: Colors.white,
size: 24,
),
),
),
],
),
);
}
@@ -438,6 +485,86 @@ class _ImmichScreenState extends State<ImmichScreen> {
}
}
void _exitSelectionMode() {
setState(() {
_isSelectionMode = false;
_selectedAssetIds.clear();
});
}
Future<void> _deleteSelectedAssets() async {
if (_selectedAssetIds.isEmpty || widget.immichService == null) {
return;
}
// Show confirmation dialog
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Assets'),
content: Text(
'Are you sure you want to delete ${_selectedAssetIds.length} asset(s)? This action cannot be undone.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
if (confirmed != true) return;
setState(() {
_isLoading = true;
});
try {
final assetIdsToDelete = _selectedAssetIds.toList();
await widget.immichService!.deleteAssets(assetIdsToDelete);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Successfully deleted ${assetIdsToDelete.length} asset(s)'),
backgroundColor: Colors.green,
),
);
// Remove deleted assets from local list
setState(() {
_assets.removeWhere((asset) => assetIdsToDelete.contains(asset.id));
_selectedAssetIds.clear();
_isSelectionMode = false;
});
// Refresh the list to ensure consistency
await _loadAssets(forceRefresh: true);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to delete assets: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
/// Formats server info map as a readable string.
String _formatServerInfo(Map<String, dynamic> info) {
final buffer = StringBuffer();