[photos][mobile] Add support for viewing HEIC images with proXDR (#1171)

## Description

New Oneplus devices have a "ProXDR" feature when viewing images. These
images (when in HEIC format) decode fine on devices that supports
ProXDR, but fails to decode on other devices.

So if decoding fails, we convert it to a JPEG and use that image for
viewing it.

## Tests

- [x] Tries converting to jpeg only if decoding fails
- [x] If converting also fails, the behaviour remains the same as before
when decoding fails.
This commit is contained in:
Vishnu Mohandas 2024-04-01 18:28:28 +05:30 committed by GitHub
commit 3e917bd855
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import "package:flutter_image_compress/flutter_image_compress.dart";
import 'package:logging/logging.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photos/core/cache/thumbnail_in_memory_cache.dart';
@ -50,6 +51,7 @@ class _ZoomableImageState extends State<ZoomableImage> {
bool _loadedLargeThumbnail = false;
bool _loadingFinalImage = false;
bool _loadedFinalImage = false;
bool _convertToSupportedFormat = false;
ValueChanged<PhotoViewScaleState>? _scaleStateChangedCallback;
bool _isZooming = false;
PhotoViewController _photoViewController = PhotoViewController();
@ -194,11 +196,8 @@ class _ZoomableImageState extends State<ZoomableImage> {
_loadingFinalImage = true;
getFileFromServer(_photo).then((file) {
if (file != null) {
_onFinalImageLoaded(
Image.file(
file,
gaplessPlayback: true,
).image,
_onFileLoaded(
file,
);
} else {
_loadingFinalImage = false;
@ -239,7 +238,9 @@ class _ZoomableImageState extends State<ZoomableImage> {
_isGIF(), // since on iOS GIFs playback only when origin-files are loaded
).then((file) {
if (file != null && file.existsSync()) {
_onFinalImageLoaded(Image.file(file).image);
_onFileLoaded(
file,
);
} else {
_logger.info("File was deleted " + _photo.toString());
if (_photo.uploadedFileID != null) {
@ -277,24 +278,45 @@ class _ZoomableImageState extends State<ZoomableImage> {
}
}
void _onFinalImageLoaded(ImageProvider imageProvider) {
void _onFileLoaded(File file) {
final imageProvider = Image.file(
file,
gaplessPlayback: true,
).image;
if (mounted) {
precacheImage(imageProvider, context).then((value) async {
if (mounted) {
await _updatePhotoViewController(
previewImageProvider: _imageProvider,
finalImageProvider: imageProvider,
);
setState(() {
_imageProvider = imageProvider;
_loadedFinalImage = true;
_logger.info("Final image loaded");
});
precacheImage(
imageProvider,
context,
onError: (exception, _) async {
_logger
.info(exception.toString() + ". Filename: ${_photo.displayName}");
if (exception.toString().contains(
"Codec failed to produce an image, possibly due to invalid image data",
)) {
unawaited(_loadInSupportedFormat(file));
}
},
).then((value) {
if (mounted && !_loadedFinalImage && !_convertToSupportedFormat) {
_updateViewWithFinalImage(imageProvider);
}
});
}
}
Future<void> _updateViewWithFinalImage(ImageProvider imageProvider) async {
await _updatePhotoViewController(
previewImageProvider: _imageProvider,
finalImageProvider: imageProvider,
);
setState(() {
_imageProvider = imageProvider;
_loadedFinalImage = true;
_logger.info("Final image loaded");
});
}
Future<void> _updatePhotoViewController({
required ImageProvider? previewImageProvider,
required ImageProvider finalImageProvider,
@ -348,4 +370,28 @@ class _ZoomableImageState extends State<ZoomableImage> {
}
bool _isGIF() => _photo.displayName.toLowerCase().endsWith(".gif");
Future<void> _loadInSupportedFormat(File file) async {
_logger.info("Compressing ${_photo.displayName} to viewable format");
_convertToSupportedFormat = true;
final compressedFile =
await FlutterImageCompress.compressWithFile(file.path);
if (compressedFile != null) {
final imageProvider = MemoryImage(compressedFile);
unawaited(
precacheImage(imageProvider, context).then((value) {
if (mounted) {
_updateViewWithFinalImage(imageProvider);
}
}),
);
} else {
_logger.severe(
"Failed to compress image ${_photo.displayName} to viewable format",
);
}
}
}