Skip to content

Commit 0677b1d

Browse files
committed
custom build for selected messages with builder
Register a builder to build the widget for selected messsages yourself. This can be useful in a multipart/alternative message, in which there is a part that you can better evaluate, for example.
1 parent 1bb80bf commit 0677b1d

File tree

2 files changed

+98
-38
lines changed

2 files changed

+98
-38
lines changed

lib/src/mime_message_downloader.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class MimeMessageDownloader extends StatefulWidget {
2828
onZoomed;
2929
final void Function(Object? exception, StackTrace? stackTrace)? onError;
3030

31+
/// With a builder you can take over the rendering for certain messages or mime types.
32+
final Widget? Function(BuildContext context, MimeMessage mimeMessage)?
33+
builder;
34+
3135
/// Creates a new message downloader widget
3236
///
3337
/// [mimeMessage] The mime message which may not be downloaded yet.
@@ -48,6 +52,7 @@ class MimeMessageDownloader extends StatefulWidget {
4852
/// Set the [onWebViewCreated] callback if you want a reference to the [InAppWebViewController].
4953
/// Set the [onZoomed] callback if you want to be notified when the webview is zoomed out after loading.
5054
/// Set the [onError] callback in case you want to be notfied about processing errors such as format exceptions.
55+
/// With a [builder] you can take over the rendering for certain messages or mime types.
5156
MimeMessageDownloader({
5257
Key? key,
5358
required this.mimeMessage,
@@ -67,6 +72,7 @@ class MimeMessageDownloader extends StatefulWidget {
6772
this.onWebViewCreated,
6873
this.onZoomed,
6974
this.onError,
75+
this.builder,
7076
}) : super(key: key);
7177

7278
@override
@@ -122,6 +128,7 @@ class _MimeMessageDownloaderState extends State<MimeMessageDownloader> {
122128
onWebViewCreated: widget.onWebViewCreated,
123129
onZoomed: widget.onZoomed,
124130
onError: widget.onError,
131+
builder: widget.builder,
125132
);
126133
}
127134

lib/src/mime_message_viewer.dart

Lines changed: 91 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,41 @@ import 'package:url_launcher/url_launcher.dart' as launcher;
1414
import 'mime_media_provider.dart';
1515

1616
/// Viewer for mime message contents
17-
class MimeMessageViewer extends StatefulWidget {
17+
class MimeMessageViewer extends StatelessWidget {
18+
/// The mime message that should be shown
1819
final MimeMessage mimeMessage;
20+
21+
/// The optional maximum width for inline images
1922
final int? maxImageWidth;
23+
24+
/// Sets if the height of this view should be set automatically, this is required to be `true` when using the MimeMessageViewer in a scrollable view.
2025
final bool adjustHeight;
26+
2127
final bool blockExternalImages;
28+
29+
/// The default text that should be shown for empty messages.
2230
final String? emptyMessageText;
31+
32+
/// Handler for mailto: links. Typically you will want to open a new compose view prepulated with a `MessageBuilder.prepareMailtoBasedMessage(uri,from)` instance.
2333
final Future Function(Uri mailto, MimeMessage mimeMessage)? mailtoDelegate;
34+
35+
/// Handler for showing the given media widget, typically in its own screen
2436
final Future Function(InteractiveMediaWidget mediaViewer)? showMediaDelegate;
37+
38+
/// Register this callback if you want a reference to the [InAppWebViewController].
2539
final void Function(InAppWebViewController controller)? onWebViewCreated;
40+
41+
/// This callback will be called when the webview zooms out after loading, usually this is a sign that the user might want to zoom in again.
2642
final void Function(InAppWebViewController controller, double zoomFactor)?
2743
onZoomed;
44+
45+
/// Is notified about any errors that might occur
2846
final void Function(Object? exception, StackTrace? stackTrace)? onError;
2947

48+
/// With a builder you can take over the rendering for certain messages or mime types.
49+
final Widget? Function(BuildContext context, MimeMessage mimeMessage)?
50+
builder;
51+
3052
/// Creates a new mime message viewer
3153
///
3254
/// [mimeMessage] The message with loaded message contents.
@@ -39,6 +61,7 @@ class MimeMessageViewer extends StatefulWidget {
3961
/// Set the [onWebViewCreated] callback if you want a reference to the [InAppWebViewController].
4062
/// Set the [onZoomed] callback if you want to be notified when the webview is zoomed out after loading.
4163
/// Set the [onError] callback in case you want to be notfied about processing errors such as format exceptions.
64+
/// With a [builder] you can take over the rendering for certain messages or mime types.
4265
MimeMessageViewer({
4366
Key? key,
4467
required this.mimeMessage,
@@ -51,14 +74,22 @@ class MimeMessageViewer extends StatefulWidget {
5174
this.onWebViewCreated,
5275
this.onZoomed,
5376
this.onError,
77+
this.builder,
5478
}) : super(key: key);
5579

5680
@override
57-
State<MimeMessageViewer> createState() {
58-
if (mimeMessage.mediaType.isImage == true) {
59-
return _ImageViewerState();
81+
Widget build(BuildContext context) {
82+
final callback = builder;
83+
if (callback != null) {
84+
final builtWidget = callback(context, mimeMessage);
85+
if (builtWidget != null) {
86+
return builtWidget;
87+
}
88+
}
89+
if (mimeMessage.mediaType.isImage) {
90+
return _ImageMimeMessageViewer(config: this);
6091
} else {
61-
return _HtmlViewerState();
92+
return _HtmlMimeMessageViewer(config: this);
6293
}
6394
}
6495
}
@@ -81,7 +112,15 @@ class _HtmlGenerationResult {
81112
_HtmlGenerationResult.error(this.errorDetails) : this.html = null;
82113
}
83114

84-
class _HtmlViewerState extends State<MimeMessageViewer> {
115+
class _HtmlMimeMessageViewer extends StatefulWidget {
116+
final MimeMessageViewer config;
117+
_HtmlMimeMessageViewer({Key? key, required this.config}) : super(key: key);
118+
119+
@override
120+
State<StatefulWidget> createState() => _HtmlViewerState();
121+
}
122+
123+
class _HtmlViewerState extends State<_HtmlMimeMessageViewer> {
85124
String? _htmlData;
86125
bool? _wereExternalImagesBlocked;
87126
bool _isGenerating = false;
@@ -92,20 +131,21 @@ class _HtmlViewerState extends State<MimeMessageViewer> {
92131

93132
@override
94133
void initState() {
95-
generateHtml(widget.blockExternalImages);
134+
generateHtml(widget.config.blockExternalImages);
96135
super.initState();
97136
}
98137

99138
void generateHtml(bool blockExternalImages) async {
100139
_wereExternalImagesBlocked = blockExternalImages;
101140
_isGenerating = true;
102-
_isHtmlMessage = widget.mimeMessage.hasPart(MediaSubtype.textHtml);
103-
final args = _HtmlGenerationArguments(widget.mimeMessage,
104-
blockExternalImages, widget.emptyMessageText, widget.maxImageWidth);
141+
final mimeMessage = widget.config.mimeMessage;
142+
_isHtmlMessage = mimeMessage.hasPart(MediaSubtype.textHtml);
143+
final args = _HtmlGenerationArguments(mimeMessage, blockExternalImages,
144+
widget.config.emptyMessageText, widget.config.maxImageWidth);
105145
final result = await compute(_generateHtmlImpl, args);
106146
_htmlData = result.html;
107147
if (_htmlData == null) {
108-
final onError = widget.onError;
148+
final onError = widget.config.onError;
109149
if (onError != null) {
110150
onError(result.errorDetails, null);
111151
}
@@ -151,11 +191,11 @@ class _HtmlViewerState extends State<MimeMessageViewer> {
151191
if (_isGenerating) {
152192
return Container(child: CircularProgressIndicator());
153193
}
154-
if (widget.blockExternalImages != _wereExternalImagesBlocked) {
155-
generateHtml(widget.blockExternalImages);
194+
if (widget.config.blockExternalImages != _wereExternalImagesBlocked) {
195+
generateHtml(widget.config.blockExternalImages);
156196
}
157197

158-
if (widget.adjustHeight) {
198+
if (widget.config.adjustHeight) {
159199
final size = MediaQuery.of(context).size;
160200
return SizedBox(
161201
height: _webViewHeight ?? size.height,
@@ -191,9 +231,9 @@ class _HtmlViewerState extends State<MimeMessageViewer> {
191231
: AndroidForceDark.FORCE_DARK_OFF,
192232
),
193233
),
194-
onWebViewCreated: widget.onWebViewCreated,
234+
onWebViewCreated: widget.config.onWebViewCreated,
195235
onLoadStop: (controller, url) async {
196-
if (widget.adjustHeight) {
236+
if (widget.config.adjustHeight) {
197237
var scrollHeight = (await controller.evaluateJavascript(
198238
source: 'document.body.scrollHeight'));
199239
// print('scrollHeight: $scrollHeight');
@@ -210,7 +250,7 @@ class _HtmlViewerState extends State<MimeMessageViewer> {
210250
}
211251
await controller.zoomBy(zoomFactor: scale, iosAnimated: true);
212252
scrollHeight = (scrollHeight * scale).ceil();
213-
final callback = widget.onZoomed;
253+
final callback = widget.config.onZoomed;
214254
if (callback != null) {
215255
callback(controller, scale);
216256
}
@@ -231,7 +271,7 @@ class _HtmlViewerState extends State<MimeMessageViewer> {
231271
action: PermissionRequestResponseAction.GRANT),
232272
);
233273
},
234-
gestureRecognizers: widget.adjustHeight
274+
gestureRecognizers: widget.config.adjustHeight
235275
? {
236276
Factory<LongPressGestureRecognizer>(
237277
() => LongPressGestureRecognizer()),
@@ -245,24 +285,27 @@ class _HtmlViewerState extends State<MimeMessageViewer> {
245285
Future<NavigationActionPolicy> shouldOverrideUrlLoading(
246286
InAppWebViewController controller, NavigationAction request) async {
247287
final requestUri = request.request.url!;
248-
if (widget.mailtoDelegate != null && requestUri.isScheme('mailto')) {
249-
await widget.mailtoDelegate!(requestUri, widget.mimeMessage);
288+
final mimeMessage = widget.config.mimeMessage;
289+
final mailtoHandler = widget.config.mailtoDelegate;
290+
if (mailtoHandler != null && requestUri.isScheme('mailto')) {
291+
await mailtoHandler(requestUri, mimeMessage);
250292
return NavigationActionPolicy.CANCEL;
251293
}
252294
if (requestUri.isScheme('cid') || requestUri.isScheme('fetch')) {
253295
// show inline part:
254296
var cid = Uri.decodeComponent(requestUri.host);
255297
final part = requestUri.isScheme('cid')
256-
? widget.mimeMessage.getPartWithContentId(cid)
257-
: widget.mimeMessage.getPart(cid);
298+
? mimeMessage.getPartWithContentId(cid)
299+
: mimeMessage.getPart(cid);
258300
if (part != null) {
259301
final mediaProvider =
260-
MimeMediaProviderFactory.fromMime(widget.mimeMessage, part);
302+
MimeMediaProviderFactory.fromMime(mimeMessage, part);
261303
final mediaWidget = InteractiveMediaWidget(
262304
mediaProvider: mediaProvider,
263305
);
264-
if (widget.showMediaDelegate != null) {
265-
widget.showMediaDelegate!(mediaWidget);
306+
final showMediaCallback = widget.config.showMediaDelegate;
307+
if (showMediaCallback != null) {
308+
showMediaCallback(mediaWidget);
266309
} else {
267310
setState(() {
268311
_mediaView = mediaWidget;
@@ -281,20 +324,29 @@ class _HtmlViewerState extends State<MimeMessageViewer> {
281324
}
282325
}
283326

327+
class _ImageMimeMessageViewer extends StatefulWidget {
328+
final MimeMessageViewer config;
329+
330+
const _ImageMimeMessageViewer({Key? key, required this.config})
331+
: super(key: key);
332+
@override
333+
State<StatefulWidget> createState() => _ImageViewerState();
334+
}
335+
284336
/// State for a message with `Content-Type: image/XXX`
285-
class _ImageViewerState extends State<MimeMessageViewer> {
286-
bool showFullScreen = false;
287-
Uint8List? imageData;
337+
class _ImageViewerState extends State<_ImageMimeMessageViewer> {
338+
bool _showFullScreen = false;
339+
Uint8List? _imageData;
288340

289341
@override
290342
void initState() {
291-
imageData = widget.mimeMessage.decodeContentBinary();
343+
_imageData = widget.config.mimeMessage.decodeContentBinary();
292344
super.initState();
293345
}
294346

295347
@override
296348
Widget build(BuildContext context) {
297-
if (showFullScreen) {
349+
if (_showFullScreen) {
298350
final screenHeight = MediaQuery.of(context).size.height;
299351
return WillPopScope(
300352
child: LayoutBuilder(
@@ -306,30 +358,31 @@ class _ImageViewerState extends State<MimeMessageViewer> {
306358
constraints: constraints,
307359
child: ImageInteractiveMedia(
308360
mediaProvider: MimeMediaProviderFactory.fromMime(
309-
widget.mimeMessage, widget.mimeMessage)),
361+
widget.config.mimeMessage, widget.config.mimeMessage)),
310362
);
311363
},
312364
),
313365
onWillPop: () {
314-
setState(() => showFullScreen = false);
366+
setState(() => _showFullScreen = false);
315367
return Future.value(false);
316368
},
317369
);
318370
} else {
319371
return TextButton(
320372
onPressed: () {
321-
if (widget.showMediaDelegate != null) {
373+
final callback = widget.config.showMediaDelegate;
374+
if (callback != null) {
322375
final mediaProvider = MimeMediaProviderFactory.fromMime(
323-
widget.mimeMessage, widget.mimeMessage);
376+
widget.config.mimeMessage, widget.config.mimeMessage);
324377
final mediaWidget =
325378
InteractiveMediaWidget(mediaProvider: mediaProvider);
326-
widget.showMediaDelegate!(mediaWidget);
379+
callback(mediaWidget);
327380
} else {
328-
setState(() => showFullScreen = true);
381+
setState(() => _showFullScreen = true);
329382
}
330383
},
331-
child: imageData != null
332-
? Image.memory(imageData!)
384+
child: _imageData != null
385+
? Image.memory(_imageData!)
333386
: Text('no image data'),
334387
);
335388
}

0 commit comments

Comments
 (0)