Skip to content

Commit fe7400b

Browse files
MrMisticMrMistic
authored andcommitted
Fix sticker rendering: add HEIC conversion, error handling, and stickerback attachment loading
- sticker_holder.dart: Add HEIC→PNG conversion in checkImage() using FlutterImageCompress (same pattern as loadAndGetProperties), wrap in try/catch, use firstWhereOrNull, add errorBuilder to Image.memory() - reaction.dart: Same HEIC→PNG conversion and error handling for STICKERBACK sticker rendering path - chat.dart: Include 'stickerback' in attachment loading filter so STICKERBACK reaction messages get their attachments loaded Fixes #121
1 parent 6a67774 commit fe7400b

3 files changed

Lines changed: 95 additions & 33 deletions

File tree

lib/app/layouts/conversation_view/widgets/message/attachment/sticker_holder.dart

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import 'dart:async';
22

33
import 'package:bluebubbles/app/wrappers/stateful_boilerplate.dart';
44
import 'package:bluebubbles/database/models.dart';
5+
import 'package:bluebubbles/helpers/helpers.dart';
56
import 'package:bluebubbles/services/services.dart';
67
import 'package:bluebubbles/utils/logger/logger.dart';
8+
import 'package:collection/collection.dart';
79
import 'package:flutter/foundation.dart';
810
import 'package:flutter/material.dart';
11+
import 'package:flutter_image_compress/flutter_image_compress.dart';
912
import 'package:universal_io/io.dart';
1013

1114
class StickerHolder extends StatefulWidget {
@@ -53,23 +56,47 @@ class _StickerHolderState extends OptimizedState<StickerHolder> with AutomaticKe
5356
}
5457

5558
Future<void> checkImage(Message message, Attachment attachment) async {
56-
final pathName = attachment.path;
57-
// Check via the image package to make sure this is a valid, render-able image
58-
// final image = await compute(decodeIsolate, PlatformFile(
59-
// path: pathName,
60-
// name: attachment.transferName!,
61-
// bytes: attachment.bytes,
62-
// size: attachment.totalBytes ?? 0,
63-
// ),
64-
// );
65-
final bytes = await File(pathName).readAsBytes();
66-
var stickerData = message.attributedBody.firstOrNull?.runs
67-
.firstWhere((element) => element.attributes?.attachmentGuid == attachment.guid).attributes?.stickerData;
68-
controller.stickerData[message.guid!] = {
69-
attachment.guid!: (bytes, stickerData)
70-
};
71-
Logger.debug("sticker count ${controller.stickerData.length}");
72-
setState(() {});
59+
try {
60+
String pathName = attachment.path;
61+
62+
// Check for HEIC and use converted PNG if available, or convert
63+
if (attachment.mimeType?.contains('image/hei') == true) {
64+
final pngPath = "$pathName.png";
65+
if (await File(pngPath).exists()) {
66+
pathName = pngPath;
67+
} else if (!kIsDesktop) {
68+
final file = await FlutterImageCompress.compressAndGetFile(
69+
pathName,
70+
pngPath,
71+
format: CompressFormat.png,
72+
keepExif: true,
73+
quality: 100,
74+
);
75+
if (file != null) {
76+
pathName = pngPath;
77+
}
78+
}
79+
}
80+
81+
// Check via the image package to make sure this is a valid, render-able image
82+
// final image = await compute(decodeIsolate, PlatformFile(
83+
// path: pathName,
84+
// name: attachment.transferName!,
85+
// bytes: attachment.bytes,
86+
// size: attachment.totalBytes ?? 0,
87+
// ),
88+
// );
89+
final bytes = await File(pathName).readAsBytes();
90+
var stickerData = message.attributedBody.firstOrNull?.runs
91+
.firstWhereOrNull((element) => element.attributes?.attachmentGuid == attachment.guid)?.attributes?.stickerData;
92+
controller.stickerData[message.guid!] = {
93+
attachment.guid!: (bytes, stickerData)
94+
};
95+
Logger.debug("sticker count ${controller.stickerData.length}");
96+
setState(() {});
97+
} catch (e, stack) {
98+
Logger.error("Failed to load sticker image", error: e, trace: stack);
99+
}
73100
}
74101

75102
@override
@@ -110,6 +137,9 @@ class _StickerHolderState extends OptimizedState<StickerHolder> with AutomaticKe
110137
gaplessPlayback: true,
111138
cacheHeight: 200,
112139
filterQuality: FilterQuality.none,
140+
errorBuilder: (context, error, stackTrace) {
141+
return const SizedBox.shrink();
142+
},
113143
),
114144
scale: e.$2?.scale ?? 1,
115145
),

lib/app/layouts/conversation_view/widgets/message/reaction/reaction.dart

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import 'package:bluebubbles/helpers/helpers.dart';
88
import 'package:bluebubbles/database/database.dart';
99
import 'package:bluebubbles/database/models.dart';
1010
import 'package:bluebubbles/services/services.dart';
11+
import 'package:bluebubbles/utils/logger/logger.dart';
1112
import 'package:defer_pointer/defer_pointer.dart';
1213
import 'package:flutter/cupertino.dart';
1314
import 'package:flutter/foundation.dart';
1415
import 'package:flutter/material.dart';
16+
import 'package:flutter_image_compress/flutter_image_compress.dart';
1517
import 'package:flutter_svg/svg.dart';
1618
import 'package:get/get.dart';
1719
import 'package:universal_io/io.dart';
@@ -89,20 +91,44 @@ class ReactionWidgetState extends OptimizedState<ReactionWidget> {
8991
}
9092

9193
Future<void> checkImage(Attachment attachment) async {
92-
final pathName = attachment.path;
93-
// Check via the image package to make sure this is a valid, render-able image
94-
// final image = await compute(decodeIsolate, PlatformFile(
95-
// path: pathName,
96-
// name: attachment.transferName!,
97-
// bytes: attachment.bytes,
98-
// size: attachment.totalBytes ?? 0,
99-
// ),
100-
// );
101-
final bytes = await File(pathName).readAsBytes();
102-
controller!.stickerData[reaction.guid!] = {
103-
attachment.guid!: (bytes, null)
104-
};
105-
setState(() {});
94+
try {
95+
String pathName = attachment.path;
96+
97+
// Check for HEIC and use converted PNG if available, or convert
98+
if (attachment.mimeType?.contains('image/hei') == true) {
99+
final pngPath = "$pathName.png";
100+
if (await File(pngPath).exists()) {
101+
pathName = pngPath;
102+
} else if (!kIsDesktop) {
103+
final file = await FlutterImageCompress.compressAndGetFile(
104+
pathName,
105+
pngPath,
106+
format: CompressFormat.png,
107+
keepExif: true,
108+
quality: 100,
109+
);
110+
if (file != null) {
111+
pathName = pngPath;
112+
}
113+
}
114+
}
115+
116+
// Check via the image package to make sure this is a valid, render-able image
117+
// final image = await compute(decodeIsolate, PlatformFile(
118+
// path: pathName,
119+
// name: attachment.transferName!,
120+
// bytes: attachment.bytes,
121+
// size: attachment.totalBytes ?? 0,
122+
// ),
123+
// );
124+
final bytes = await File(pathName).readAsBytes();
125+
controller!.stickerData[reaction.guid!] = {
126+
attachment.guid!: (bytes, null)
127+
};
128+
setState(() {});
129+
} catch (e, stack) {
130+
Logger.error("Failed to load reaction sticker image", error: e, trace: stack);
131+
}
106132
}
107133

108134
void updateReaction() async {
@@ -208,6 +234,9 @@ class ReactionWidgetState extends OptimizedState<ReactionWidget> {
208234
gaplessPlayback: true,
209235
cacheHeight: 200,
210236
filterQuality: FilterQuality.none,
237+
errorBuilder: (context, error, stackTrace) {
238+
return const SizedBox.shrink();
239+
},
211240
),
212241
) : const SizedBox.shrink();
213242
}
@@ -273,6 +302,9 @@ class ReactionWidgetState extends OptimizedState<ReactionWidget> {
273302
gaplessPlayback: true,
274303
cacheHeight: 200,
275304
filterQuality: FilterQuality.none,
305+
errorBuilder: (context, error, stackTrace) {
306+
return const SizedBox.shrink();
307+
},
276308
),
277309
) : const SizedBox.shrink();
278310
}

lib/database/io/chat.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ class GetMessages extends AsyncTask<List<dynamic>, List<Message>> {
160160
associatedMessagesQuery.close();
161161
associatedMessages = MessageHelper.normalizedAssociatedMessages(associatedMessages);
162162
for (Message m in associatedMessages) {
163-
if (m.associatedMessageType != "sticker") continue;
163+
if (m.associatedMessageType != "sticker" && m.associatedMessageType != "stickerback") continue;
164164
m.attachments = List<Attachment>.from(m.dbAttachments);
165165
}
166166
for (Message m in messages) {
@@ -235,7 +235,7 @@ class AddMessages extends AsyncTask<List<dynamic>, List<Message>> {
235235
/// Assign the relevant attachments and associated messages to the original
236236
/// messages
237237
for (Message m in associatedMessages) {
238-
if (m.associatedMessageType != "sticker") continue;
238+
if (m.associatedMessageType != "sticker" && m.associatedMessageType != "stickerback") continue;
239239
m.attachments = List<Attachment>.from(m.dbAttachments);
240240
}
241241
for (Message m in newMessages) {

0 commit comments

Comments
 (0)