Skip to content

Commit dde65b1

Browse files
committed
feat: added-image-library-section.
1 parent 1c6f8fc commit dde65b1

25 files changed

Lines changed: 2627 additions & 4 deletions
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:magic_epaper_app/image_library/model/saved_image_model.dart';
3+
import 'package:magic_epaper_app/image_library/provider/image_library_provider.dart';
4+
import 'package:magic_epaper_app/image_library/services/image_operations_service.dart';
5+
import 'package:magic_epaper_app/image_library/widgets/app_bar_widget.dart';
6+
import 'package:magic_epaper_app/image_library/widgets/dialogs/batch_delete_confirmation_dialog.dart';
7+
import 'package:magic_epaper_app/image_library/widgets/dialogs/delete_confirmation_dialog.dart';
8+
import 'package:magic_epaper_app/image_library/widgets/empty_state_widget.dart';
9+
import 'package:magic_epaper_app/image_library/widgets/image_grid_widget.dart';
10+
import 'package:magic_epaper_app/image_library/widgets/dialogs/image_preview_dialog.dart';
11+
import 'package:magic_epaper_app/image_library/widgets/search_and_filter_widget.dart';
12+
import 'package:magic_epaper_app/constants/color_constants.dart';
13+
import 'package:provider/provider.dart';
14+
15+
class ImageLibraryScreen extends StatefulWidget {
16+
const ImageLibraryScreen({super.key});
17+
18+
@override
19+
State<ImageLibraryScreen> createState() => _ImageLibraryScreenState();
20+
}
21+
22+
class _ImageLibraryScreenState extends State<ImageLibraryScreen> {
23+
final TextEditingController _searchController = TextEditingController();
24+
bool _isDeleteMode = false;
25+
Set<String> _selectedImages = <String>{};
26+
late ImageOperationsService _operationsService;
27+
28+
@override
29+
void initState() {
30+
super.initState();
31+
_operationsService = ImageOperationsService(context);
32+
WidgetsBinding.instance.addPostFrameCallback((_) {
33+
context.read<ImageLibraryProvider>().loadSavedImages();
34+
});
35+
}
36+
37+
@override
38+
void dispose() {
39+
_searchController.dispose();
40+
super.dispose();
41+
}
42+
43+
void _handleImageTap(SavedImage image) {
44+
if (_isDeleteMode) {
45+
_toggleImageSelection(image.id);
46+
} else {
47+
_showImagePreview(image);
48+
}
49+
}
50+
51+
void _toggleImageSelection(String imageId) {
52+
setState(() {
53+
if (_selectedImages.contains(imageId)) {
54+
_selectedImages.remove(imageId);
55+
} else {
56+
_selectedImages.add(imageId);
57+
}
58+
});
59+
}
60+
61+
void _showImagePreview(SavedImage image) {
62+
final provider = context.read<ImageLibraryProvider>();
63+
64+
showDialog(
65+
context: context,
66+
builder: (context) => ImagePreviewDialog(
67+
image: image,
68+
epd: _operationsService.getEpdFromImage(image),
69+
onDelete: () => _showDeleteDialog(image, provider),
70+
onRename: (newName) =>
71+
_operationsService.renameImage(image, newName, provider),
72+
onTransfer: () => _operationsService.transferSingleImage(image),
73+
),
74+
);
75+
}
76+
77+
void _showDeleteDialog(SavedImage image, ImageLibraryProvider provider) {
78+
showDialog(
79+
context: context,
80+
barrierDismissible: false,
81+
builder: (context) => DeleteConfirmationDialog(
82+
image: image,
83+
onConfirm: () => _operationsService.deleteImage(image, provider),
84+
),
85+
);
86+
}
87+
88+
void _showBatchDeleteDialog() {
89+
final provider = context.read<ImageLibraryProvider>();
90+
final selectedImageObjects = provider.savedImages
91+
.where((image) => _selectedImages.contains(image.id))
92+
.toList();
93+
94+
showDialog(
95+
context: context,
96+
barrierDismissible: false,
97+
builder: (context) => BatchDeleteConfirmationDialog(
98+
selectedImages: selectedImageObjects,
99+
onConfirm: () => _performBatchDelete(selectedImageObjects, provider),
100+
),
101+
);
102+
}
103+
104+
Future<void> _performBatchDelete(
105+
List<SavedImage> selectedImages,
106+
ImageLibraryProvider provider,
107+
) async {
108+
await _operationsService.batchDeleteImages(selectedImages, provider);
109+
_exitDeleteMode();
110+
}
111+
112+
void _exitDeleteMode() {
113+
setState(() {
114+
_isDeleteMode = false;
115+
_selectedImages.clear();
116+
});
117+
}
118+
119+
void _enterDeleteMode() {
120+
setState(() {
121+
_isDeleteMode = true;
122+
_selectedImages.clear();
123+
});
124+
}
125+
126+
@override
127+
Widget build(BuildContext context) {
128+
return Scaffold(
129+
backgroundColor: Colors.white,
130+
appBar: LibraryAppBar(
131+
isDeleteMode: _isDeleteMode,
132+
selectedCount: _selectedImages.length,
133+
onDeletePressed: _showBatchDeleteDialog,
134+
onExitDeleteMode: _exitDeleteMode,
135+
onEnterDeleteMode: _enterDeleteMode,
136+
),
137+
body: Consumer<ImageLibraryProvider>(
138+
builder: (context, provider, child) {
139+
if (provider.isLoading) {
140+
return const Center(
141+
child: CircularProgressIndicator(color: colorAccent),
142+
);
143+
}
144+
145+
if (provider.savedImages.isEmpty) {
146+
return const EmptyStateWidget();
147+
}
148+
149+
return Column(
150+
children: [
151+
SearchAndFilterWidget(
152+
searchController: _searchController,
153+
provider: provider,
154+
),
155+
Expanded(
156+
child: ImageGridWidget(
157+
images: provider.filteredImages,
158+
isDeleteMode: _isDeleteMode,
159+
selectedImages: _selectedImages,
160+
onImageTap: _handleImageTap,
161+
),
162+
),
163+
],
164+
);
165+
},
166+
),
167+
);
168+
}
169+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'dart:typed_data';
2+
import 'dart:convert';
3+
4+
class SavedImage {
5+
final String id;
6+
final String name;
7+
final Uint8List imageData;
8+
final DateTime createdAt;
9+
final String source;
10+
final Map<String, dynamic>? metadata;
11+
12+
SavedImage({
13+
required this.id,
14+
required this.name,
15+
required this.imageData,
16+
required this.createdAt,
17+
required this.source,
18+
this.metadata,
19+
});
20+
21+
Map<String, dynamic> toJson() {
22+
return {
23+
'id': id,
24+
'name': name,
25+
'imageData': base64Encode(imageData),
26+
'createdAt': createdAt.toIso8601String(),
27+
'source': source,
28+
'metadata': metadata,
29+
};
30+
}
31+
32+
factory SavedImage.fromJson(Map<String, dynamic> json) {
33+
Uint8List imageData;
34+
final imageDataJson = json['imageData'];
35+
36+
if (imageDataJson is String) {
37+
imageData = base64Decode(imageDataJson);
38+
} else if (imageDataJson is List) {
39+
print("this method");
40+
41+
imageData = Uint8List.fromList(List<int>.from(imageDataJson));
42+
} else {
43+
throw FormatException('Invalid imageData format');
44+
}
45+
46+
return SavedImage(
47+
id: json['id'],
48+
name: json['name'],
49+
imageData: imageData,
50+
createdAt: DateTime.parse(json['createdAt']),
51+
source: json['source'],
52+
metadata: json['metadata'],
53+
);
54+
}
55+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import 'dart:typed_data';
2+
import 'package:flutter/material.dart';
3+
import 'package:magic_epaper_app/image_library/model/saved_image_model.dart';
4+
import 'package:shared_preferences/shared_preferences.dart';
5+
import 'dart:convert';
6+
7+
class ImageLibraryProvider extends ChangeNotifier {
8+
List<SavedImage> _savedImages = [];
9+
List<SavedImage> get savedImages => _savedImages;
10+
11+
bool _isLoading = false;
12+
bool get isLoading => _isLoading;
13+
14+
String _searchQuery = '';
15+
String get searchQuery => _searchQuery;
16+
17+
String _selectedSource = 'all';
18+
String get selectedSource => _selectedSource;
19+
20+
List<SavedImage> get filteredImages {
21+
var filtered = _savedImages.where((image) {
22+
final matchesSearch =
23+
image.name.toLowerCase().contains(_searchQuery.toLowerCase());
24+
final matchesSource =
25+
_selectedSource == 'all' || image.source == _selectedSource;
26+
return matchesSearch && matchesSource;
27+
}).toList();
28+
29+
filtered.sort((a, b) => b.createdAt.compareTo(a.createdAt));
30+
return filtered;
31+
}
32+
33+
Future<void> loadSavedImages() async {
34+
_isLoading = true;
35+
notifyListeners();
36+
37+
try {
38+
final prefs = await SharedPreferences.getInstance();
39+
final savedImagesJson = prefs.getStringList('saved_images') ?? [];
40+
41+
_savedImages = savedImagesJson
42+
.map((json) => SavedImage.fromJson(jsonDecode(json)))
43+
.toList();
44+
} catch (e) {
45+
debugPrint('Error loading saved images: $e');
46+
}
47+
48+
_isLoading = false;
49+
notifyListeners();
50+
}
51+
52+
Future<void> saveImage({
53+
required String name,
54+
required Uint8List imageData,
55+
required String source,
56+
Map<String, dynamic>? metadata,
57+
}) async {
58+
final savedImage = SavedImage(
59+
id: DateTime.now().millisecondsSinceEpoch.toString(),
60+
name: name,
61+
imageData: imageData,
62+
createdAt: DateTime.now(),
63+
source: source,
64+
metadata: metadata,
65+
);
66+
67+
_savedImages.add(savedImage);
68+
await _persistImages();
69+
notifyListeners();
70+
}
71+
72+
Future<void> deleteImage(String id) async {
73+
_savedImages.removeWhere((image) => image.id == id);
74+
await _persistImages();
75+
notifyListeners();
76+
}
77+
78+
Future<void> renameImage(String id, String newName) async {
79+
final index = _savedImages.indexWhere((image) => image.id == id);
80+
if (index != -1) {
81+
final oldImage = _savedImages[index];
82+
_savedImages[index] = SavedImage(
83+
id: oldImage.id,
84+
name: newName,
85+
imageData: oldImage.imageData,
86+
createdAt: oldImage.createdAt,
87+
source: oldImage.source,
88+
metadata: oldImage.metadata,
89+
);
90+
await _persistImages();
91+
notifyListeners();
92+
}
93+
}
94+
95+
void updateSearchQuery(String query) {
96+
_searchQuery = query;
97+
notifyListeners();
98+
}
99+
100+
void updateSourceFilter(String source) {
101+
_selectedSource = source;
102+
notifyListeners();
103+
}
104+
105+
Future<void> _persistImages() async {
106+
try {
107+
final prefs = await SharedPreferences.getInstance();
108+
final savedImagesJson =
109+
_savedImages.map((image) => jsonEncode(image.toJson())).toList();
110+
await prefs.setStringList('saved_images', savedImagesJson);
111+
} catch (e) {
112+
debugPrint('Error persisting images: $e');
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)