Skip to content

Commit 24d158a

Browse files
authored
fix: prioritize add and recent cliparts in picker (#1684)
1 parent 43864ea commit 24d158a

11 files changed

Lines changed: 163 additions & 86 deletions

lib/bademagic_module/utils/converters.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,14 @@ class Converters {
314314
.toList());
315315
} else if (segment['type'] == 'image') {
316316
int index = segment['index'];
317-
var key = controllerData.imageCache.keys.toList()[index];
317+
318+
final key = controllerData.imageCache.keys.firstWhere(
319+
(cacheKey) =>
320+
cacheKey == index ||
321+
(cacheKey is List && cacheKey.length > 1 && cacheKey[1] == index),
322+
orElse: () => index,
323+
);
324+
318325
if (key is List) {
319326
String filename = key[0];
320327
List<dynamic>? decodedData = await fileHelper.readFromFile(filename);

lib/bademagic_module/utils/file_helper.dart

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,17 @@ class FileHelper {
4242
void addToCache(Uint8List imageData, String filename) {
4343
int key;
4444
if (imageCacheProvider.availableKeys.isNotEmpty) {
45-
// Reuse the lowest available key
4645
key = imageCacheProvider.availableKeys.first;
4746
imageCacheProvider.availableKeys.remove(key);
4847
} else {
49-
// Assign a new key
5048
key = imageCacheProvider.imageCache.length;
5149
while (imageCacheProvider.imageCache.containsKey(key)) {
5250
key++;
5351
}
5452
}
53+
5554
imageCacheProvider.imageCache[[filename, key]] = imageData;
55+
imageCacheProvider.notify();
5656
}
5757

5858
Future<void> generateClipartCache() async {
@@ -76,7 +76,7 @@ class FileHelper {
7676
decodedData.cast<List<dynamic>>();
7777
List<List<int>> intImageData =
7878
imageData.map((list) => list.cast<int>()).toList();
79-
imageCacheProvider.clipartsCache[file.path.split('/').last] =
79+
imageCacheProvider.clipartsCache[file.uri.pathSegments.last] =
8080
intImageData;
8181
}
8282
} catch (e) {
@@ -137,11 +137,18 @@ class FileHelper {
137137

138138
// Read all files, parse the 2D lists, and add to cache
139139
Future<void> loadImageCacheFromFiles() async {
140-
generateClipartCache();
141-
getBadgeDataFiles();
140+
await generateClipartCache();
141+
await getBadgeDataFiles();
142142
final directory = await getApplicationDocumentsDirectory();
143143
final List<FileSystemEntity> files = directory.listSync();
144144

145+
files.sort((a, b) {
146+
if (a is File && b is File) {
147+
return b.lastModifiedSync().compareTo(a.lastModifiedSync());
148+
}
149+
return 0;
150+
});
151+
145152
for (var file in files) {
146153
if (file is File &&
147154
file.path.endsWith('.json') &&
@@ -152,7 +159,7 @@ class FileHelper {
152159
final List<dynamic> decodedData = jsonDecode(content);
153160
final List<List<dynamic>> imageData =
154161
decodedData.cast<List<dynamic>>();
155-
await _addImageDataToCache(imageData, file.path.split('/').last);
162+
await _addImageDataToCache(imageData, file.uri.pathSegments.last);
156163
}
157164
}
158165
}
@@ -308,7 +315,7 @@ class FileHelper {
308315
// Defensive: Only add if valid structure
309316
if (jsonData.containsKey('messages') &&
310317
jsonData['messages'] is List) {
311-
badgeDataList.add(MapEntry(file.path.split('/').last, jsonData));
318+
badgeDataList.add(MapEntry(file.uri.pathSegments.last, jsonData));
312319
} else {
313320
logger.i('Skipping invalid badge file: ${file.path}');
314321
}
@@ -364,7 +371,8 @@ class FileHelper {
364371
File file = File(filePath);
365372
if (await file.exists()) {
366373
// Use share_plus to share the file
367-
final result = await Share.shareXFiles([XFile(filePath)]);
374+
final result = await SharePlus.instance
375+
.share(ShareParams(files: [XFile(filePath)]));
368376
if (result.status == ShareResultStatus.success) {
369377
logger.i('File shared successfully');
370378
} else {

lib/providers/imageprovider.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,15 @@ class InlineImageProvider extends ChangeNotifier {
112112
//The cache generation time acts as a delay in the splash screen
113113
Map<Object, Uint8List?> imageCache = {};
114114

115-
void removeFromCache(String key) {
116-
imageCache.remove(key);
117-
logger.d('Removed from cache: ${imageCache.containsKey(key)}');
115+
void notify() {
116+
notifyListeners();
117+
}
118+
119+
void removeFromCache(String filename) {
120+
imageCache.removeWhere(
121+
(key, value) => key is List && key.isNotEmpty && key[0] == filename,
122+
);
123+
logger.d('Removed from cache: $filename');
118124
notifyListeners();
119125
}
120126

@@ -131,7 +137,7 @@ class InlineImageProvider extends ChangeNotifier {
131137
var unit8List = byteData!.buffer.asUint8List();
132138
imageCache[x] = unit8List;
133139
}
134-
fileHelper.loadImageCacheFromFiles();
140+
await fileHelper.loadImageCacheFromFiles();
135141
notifyListeners();
136142
}
137143

lib/view/saved_clipart.dart

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart';
22
import 'package:badgemagic/bademagic_module/utils/file_helper.dart';
33
import 'package:badgemagic/constants.dart';
4-
import 'package:badgemagic/services/localization_service.dart';
54
import 'package:badgemagic/providers/imageprovider.dart';
5+
import 'package:badgemagic/services/localization_service.dart';
66
import 'package:badgemagic/view/widgets/clipart_list_view.dart';
77
import 'package:badgemagic/view/widgets/common_scaffold_widget.dart';
88
import 'package:flutter/material.dart';
@@ -38,6 +38,7 @@ class _SavedClipartState extends State<SavedClipart> {
3838
@override
3939
Widget build(BuildContext context) {
4040
final l10n = GetIt.instance.get<LocalizationService>().l10n;
41+
4142
return CommonScaffold(
4243
index: 3,
4344
key: const Key(savedClipartScreen),
@@ -54,33 +55,35 @@ class _SavedClipartState extends State<SavedClipart> {
5455
height: 200.h,
5556
),
5657
),
57-
SizedBox(
58-
height: 20.h,
58+
SizedBox(height: 20.h),
59+
Text(
60+
l10n.noSavedClipart,
61+
style: TextStyle(
62+
color: Colors.black,
63+
fontSize: 20.sp,
64+
),
65+
),
66+
Text(
67+
l10n.noSavedClipartMessage,
68+
style: TextStyle(
69+
color: Colors.black,
70+
fontSize: 14.sp,
71+
),
5972
),
60-
Text(l10n.noSavedClipart,
61-
style: TextStyle(
62-
color: Colors.black,
63-
fontSize: 20.sp,
64-
)),
65-
Text(l10n.noSavedClipartMessage,
66-
style: TextStyle(
67-
color: Colors.black,
68-
fontSize: 14.sp,
69-
)),
7073
],
7174
),
7275
)
7376
: SavedClipartListView(
7477
images: imageprovider.clipartsCache,
7578
refreshClipartCallback: (String fileName) async {
7679
imageprovider.clipartsCache.remove(fileName);
80+
imageprovider.removeFromCache(fileName);
81+
7782
setState(() {
7883
logger.i('Clipart $fileName deleted');
7984
});
80-
imageprovider.removeFromCache(fileName);
81-
imageprovider.generateImageCache();
8285
},
83-
), // Use the separate ListView widget here
86+
),
8487
);
8588
}
8689
}

lib/view/special_text_field.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ class InlineImage extends SpecialText {
1818
if (key.length > 4 && key.startsWith('<<') && key.endsWith('>>')) {
1919
try {
2020
final int index = int.parse(key.substring(2, key.length - 2));
21-
var vectorIndex = textData.imageCache.keys.toList()[index];
21+
final vectorIndex = textData.imageCache.keys.firstWhere(
22+
(cacheKey) =>
23+
cacheKey == index ||
24+
(cacheKey is List && cacheKey.length > 1 && cacheKey[1] == index),
25+
orElse: () => index,
26+
);
2227

2328
final image = textData.imageCache[vectorIndex];
2429
if (image != null) {

lib/view/widgets/clipart_list_view.dart

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class SavedClipartListView extends StatelessWidget {
1212
final FileHelper file = FileHelper();
1313
final ImageUtils imageUtils = ImageUtils();
1414

15-
final void Function(String) refreshClipartCallback;
15+
final Future<void> Function(String) refreshClipartCallback;
1616

1717
SavedClipartListView({
1818
super.key,
@@ -26,9 +26,15 @@ class SavedClipartListView extends StatelessWidget {
2626
padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 15.w),
2727
itemCount: images.length,
2828
itemBuilder: (context, index) {
29-
Future<Uint8List?> image = imageUtils
30-
.convert2DListToUint8List(images.values.elementAt(index)!);
31-
String fileName = images.keys.elementAt(index);
29+
final imageData = images.values.elementAt(index);
30+
final String fileName = images.keys.elementAt(index);
31+
32+
if (imageData == null) {
33+
return const SizedBox.shrink();
34+
}
35+
36+
final Future<Uint8List?> image =
37+
imageUtils.convert2DListToUint8List(imageData);
3238

3339
return Container(
3440
margin: EdgeInsets.only(bottom: 12.h),
@@ -83,12 +89,15 @@ class SavedClipartListView extends StatelessWidget {
8389
padding: EdgeInsets.all(8.dg),
8490
),
8591
onPressed: () {
86-
Navigator.of(context).push(MaterialPageRoute(
92+
Navigator.of(context).push(
93+
MaterialPageRoute(
8794
builder: (context) => DrawBadge(
88-
filename: fileName,
89-
isSavedClipart: true,
90-
badgeGrid: images.values.elementAt(index),
91-
)));
95+
filename: fileName,
96+
isSavedClipart: true,
97+
badgeGrid: imageData,
98+
),
99+
),
100+
);
92101
},
93102
icon: const Icon(Icons.edit_outlined),
94103
),
@@ -100,13 +109,12 @@ class SavedClipartListView extends StatelessWidget {
100109
padding: EdgeInsets.all(8.dg),
101110
),
102111
icon: const Icon(Icons.delete_outline_rounded),
103-
onPressed: () {
104-
_showDeleteDialog(context).then((value) async {
105-
if (value) {
106-
await file.deleteFile(fileName);
107-
refreshClipartCallback(fileName);
108-
}
109-
});
112+
onPressed: () async {
113+
final value = await _showDeleteDialog(context);
114+
if (value) {
115+
await file.deleteFile(fileName);
116+
await refreshClipartCallback(fileName);
117+
}
110118
},
111119
),
112120
],
@@ -117,11 +125,12 @@ class SavedClipartListView extends StatelessWidget {
117125
}
118126

119127
Future<bool> _showDeleteDialog(BuildContext context) async {
120-
return await showDialog(
121-
context: context,
122-
builder: (BuildContext context) {
123-
return const DeleteBadgeDialog();
124-
},
125-
);
128+
return await showDialog<bool>(
129+
context: context,
130+
builder: (BuildContext context) {
131+
return const DeleteBadgeDialog();
132+
},
133+
) ??
134+
false;
126135
}
127136
}

lib/view/widgets/save_badge_card.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class SaveBadgeCard extends StatelessWidget {
4747
borderRadius: BorderRadius.circular(6.dg),
4848
boxShadow: [
4949
BoxShadow(
50-
color: Colors.grey.withOpacity(0.5),
50+
color: Colors.grey.withValues(alpha: 0.5),
5151
spreadRadius: 2,
5252
blurRadius: 5,
5353
offset: const Offset(0, 3),
@@ -103,12 +103,13 @@ class SaveBadgeCard extends StatelessWidget {
103103
color: Colors.black,
104104
),
105105
onPressed: () async {
106+
final navigator = Navigator.of(context);
106107
// Show confirmation dialog before editing
107108
final confirmed =
108109
await provider.showEditBadgeConfirmation(context);
109110
if (confirmed) {
110111
// Navigate to HomeScreen for editing the badge
111-
Navigator.of(context).push(
112+
navigator.push(
112113
MaterialPageRoute(
113114
builder: (context) => HomeScreen(
114115
savedBadgeFilename: badgeData.key,

lib/view/widgets/save_badge_dialog.dart

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class SaveBadgeDialog extends StatelessWidget {
137137
bool caseSensitiveExists = await file.exists();
138138

139139
if (caseSensitiveExists) {
140+
if (!context.mounted) return;
140141
final result = await showDialog<String>(
141142
context: context,
142143
builder: (context) => AlertDialog(
@@ -172,15 +173,18 @@ class SaveBadgeDialog extends StatelessWidget {
172173
content: Text(l10n.badgeUpdatedSuccessfully)),
173174
);
174175
Future.delayed(const Duration(milliseconds: 100), () {
175-
Navigator.of(context, rootNavigator: true)
176-
.pushNamedAndRemoveUntil(
177-
'/savedBadge', (route) => false);
176+
if (context.mounted) {
177+
Navigator.of(context, rootNavigator: true)
178+
.pushNamedAndRemoveUntil(
179+
'/savedBadge', (route) => false);
180+
}
178181
});
179182
return;
180183
} else {
181184
return;
182185
}
183186
} else if (caseInsensitiveMatch != null) {
187+
if (!context.mounted) return;
184188
final result = await showDialog<String>(
185189
context: context,
186190
builder: (context) => AlertDialog(
@@ -229,9 +233,11 @@ class SaveBadgeDialog extends StatelessWidget {
229233
content: Text(l10n.badgeUpdatedSuccessfully)),
230234
);
231235
Future.delayed(const Duration(milliseconds: 100), () {
232-
Navigator.of(context, rootNavigator: true)
233-
.pushNamedAndRemoveUntil(
234-
'/savedBadge', (route) => false);
236+
if (context.mounted) {
237+
Navigator.of(context, rootNavigator: true)
238+
.pushNamedAndRemoveUntil(
239+
'/savedBadge', (route) => false);
240+
}
235241
});
236242
return;
237243
} else {
@@ -250,7 +256,9 @@ class SaveBadgeDialog extends StatelessWidget {
250256
ScaffoldMessenger.of(context).showSnackBar(
251257
SnackBar(content: Text(l10n.badgeSavedSuccessfully)),
252258
);
253-
Navigator.of(context).pop();
259+
if (context.mounted) {
260+
Navigator.of(context).pop();
261+
}
254262
}
255263
},
256264
child: Text(

0 commit comments

Comments
 (0)