improved performance and battery usage on older devices
This commit is contained in:
@@ -10,9 +10,33 @@ class PmImageView extends StatelessWidget {
|
||||
|
||||
const PmImageView({required this.node, super.key});
|
||||
|
||||
/// Full-resolution source, used for the zoomable fullscreen view.
|
||||
static ImageProvider? sourceProvider(PmImage node) {
|
||||
final bytes = node.bytes;
|
||||
if (bytes != null) return MemoryImage(bytes);
|
||||
if (node.src.startsWith('http')) {
|
||||
return CachedNetworkImageProvider(node.src);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Inline images never render wider than the screen, so they are decoded at
|
||||
/// most at its physical width instead of the (often camera-sized) original.
|
||||
/// [PmJsonView] precaches through this too, so both hit the same cache key.
|
||||
static ImageProvider? inlineProvider(BuildContext context, PmImage node) {
|
||||
final source = sourceProvider(node);
|
||||
if (source == null) return null;
|
||||
final width =
|
||||
(MediaQuery.sizeOf(context).width *
|
||||
MediaQuery.devicePixelRatioOf(context))
|
||||
.round();
|
||||
return ResizeImage(source, width: width);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = _imageProvider();
|
||||
final provider = sourceProvider(node);
|
||||
final inline = inlineProvider(context, node);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final available = constraints.maxWidth.isFinite
|
||||
@@ -22,7 +46,13 @@ class PmImageView extends StatelessWidget {
|
||||
|
||||
Widget image = ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: targetWidth ?? available),
|
||||
child: _imageWidget(context, provider),
|
||||
child: inline == null
|
||||
? _brokenImage(context)
|
||||
: Image(
|
||||
image: inline,
|
||||
errorBuilder: (context, error, stack) =>
|
||||
_brokenImage(context),
|
||||
),
|
||||
);
|
||||
|
||||
final href = node.href;
|
||||
@@ -43,30 +73,6 @@ class PmImageView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
ImageProvider? _imageProvider() {
|
||||
if (node.bytes != null) return MemoryImage(node.bytes!);
|
||||
if (node.src.startsWith('http')) {
|
||||
return CachedNetworkImageProvider(node.src);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget _imageWidget(BuildContext context, ImageProvider? provider) {
|
||||
if (node.bytes != null) {
|
||||
return Image.memory(
|
||||
node.bytes!,
|
||||
errorBuilder: (context, error, stack) => _brokenImage(context),
|
||||
);
|
||||
}
|
||||
if (node.src.startsWith('http')) {
|
||||
return CachedNetworkImage(
|
||||
imageUrl: node.src,
|
||||
errorWidget: (context, url, error) => _brokenImage(context),
|
||||
);
|
||||
}
|
||||
return _brokenImage(context);
|
||||
}
|
||||
|
||||
Widget _brokenImage(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return DecoratedBox(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../app_progress_indicator.dart';
|
||||
import 'pm_document_view.dart';
|
||||
import 'pm_image_view.dart';
|
||||
import 'pm_node.dart';
|
||||
|
||||
/// Renders raw ProseMirror JSON and memoises the parsed tree across rebuilds.
|
||||
@@ -31,7 +31,7 @@ class PmJsonView extends StatefulWidget {
|
||||
class _PmJsonViewState extends State<PmJsonView> {
|
||||
PmNode? _shown;
|
||||
PmNode? _pending;
|
||||
List<ImageProvider> _pendingProviders = const [];
|
||||
List<PmImage> _pendingImages = const [];
|
||||
bool _precacheStarted = false;
|
||||
int _generation = 0;
|
||||
|
||||
@@ -62,17 +62,17 @@ class _PmJsonViewState extends State<PmJsonView> {
|
||||
|
||||
void _parse() {
|
||||
final doc = PmNode.fromJson(widget.json);
|
||||
final providers = <ImageProvider>[];
|
||||
_collectProviders(doc, providers);
|
||||
final images = <PmImage>[];
|
||||
_collectImages(doc, images);
|
||||
_generation++;
|
||||
_precacheStarted = false;
|
||||
if (providers.isEmpty) {
|
||||
if (images.isEmpty) {
|
||||
_shown = doc;
|
||||
_pending = null;
|
||||
_pendingProviders = const [];
|
||||
_pendingImages = const [];
|
||||
} else {
|
||||
_pending = doc;
|
||||
_pendingProviders = providers;
|
||||
_pendingImages = images;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,9 @@ class _PmJsonViewState extends State<PmJsonView> {
|
||||
_precacheStarted = true;
|
||||
final generation = _generation;
|
||||
Future.wait([
|
||||
for (final provider in _pendingProviders)
|
||||
precacheImage(provider, context, onError: (_, _) {}),
|
||||
for (final image in _pendingImages)
|
||||
if (PmImageView.inlineProvider(context, image) case final provider?)
|
||||
precacheImage(provider, context, onError: (_, _) {}),
|
||||
])
|
||||
.timeout(PmJsonView.precacheTimeout, onTimeout: () => const [])
|
||||
.whenComplete(() {
|
||||
@@ -91,23 +92,16 @@ class _PmJsonViewState extends State<PmJsonView> {
|
||||
setState(() {
|
||||
_shown = pending;
|
||||
_pending = null;
|
||||
_pendingProviders = const [];
|
||||
_pendingImages = const [];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _collectProviders(PmNode node, List<ImageProvider> out) {
|
||||
if (node is PmImage) {
|
||||
final bytes = node.bytes;
|
||||
if (bytes != null) {
|
||||
out.add(MemoryImage(bytes));
|
||||
} else if (node.src.startsWith('http')) {
|
||||
out.add(CachedNetworkImageProvider(node.src));
|
||||
}
|
||||
}
|
||||
void _collectImages(PmNode node, List<PmImage> out) {
|
||||
if (node is PmImage) out.add(node);
|
||||
for (final child in node.children) {
|
||||
_collectProviders(child, out);
|
||||
_collectImages(child, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user