From db0d30e5a294f946e2faf5a5507cdac61958105a Mon Sep 17 00:00:00 2001 From: OleS Date: Tue, 20 Jan 2026 16:48:40 +0200 Subject: [PATCH] * Fix: imageURL download time improvement --- CHANGELOG.md | 5 +- .../utils/virtusize_product_image_loader.dart | 62 +++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da87407..9b0a26d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ +## Changes: +* Fix: imageURL download time improvement + ## 2.2.8 -* Fix recommended size is shown in InPage even when there is no available size in the comparison screen +* Fix: recommended size is shown in InPage even when there is no available size in the comparison screen * Fix: Virtusize button state when loading a new product * Fix: Android: InPage Standard sometimes does not appear * Fix: Android: VS widget sometimes does not show when navigating between different colors diff --git a/lib/src/utils/virtusize_product_image_loader.dart b/lib/src/utils/virtusize_product_image_loader.dart index 84962dd..303f2a7 100644 --- a/lib/src/utils/virtusize_product_image_loader.dart +++ b/lib/src/utils/virtusize_product_image_loader.dart @@ -3,15 +3,65 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:virtusize_flutter_sdk/src/models/virtusize_server_product.dart'; +const _imageLoadTimeout = Duration(seconds: 10); + Future downloadProductImage(VirtusizeServerProduct product) async { - var image = await _downloadImage(product.imageURL ?? ''); - image ??= await _downloadImage(product.cloudinaryImageURL ?? ''); + final imageURL = product.imageURL; + final cloudinaryImageURL = product.cloudinaryImageURL; + + // Build list of valid URLs to try in parallel + final futures = >[]; + if (imageURL != null && imageURL.isNotEmpty) { + futures.add(_downloadImageWithTimeout(imageURL)); + } + if (cloudinaryImageURL != null && cloudinaryImageURL.isNotEmpty) { + futures.add(_downloadImageWithTimeout(cloudinaryImageURL)); + } + + if (futures.isEmpty) return; + + // Try all URLs in parallel, use the first successful result + final image = await _firstSuccessful(futures); if (image != null) { product.networkProductImage = image; } } +/// Returns the first successful (non-null) result from the futures. +/// If all futures complete with null, returns null. +Future _firstSuccessful(List> futures) async { + if (futures.isEmpty) return null; + + final completer = Completer(); + var pendingCount = futures.length; + var completed = false; + + for (final future in futures) { + future.then((image) { + if (completed) return; + if (image != null) { + completed = true; + completer.complete(image); + } else { + pendingCount--; + if (pendingCount == 0 && !completed) { + completed = true; + completer.complete(null); + } + } + }); + } + + return completer.future; +} + +Future _downloadImageWithTimeout(String imageUrl) { + return _downloadImage( + imageUrl, + ).timeout(_imageLoadTimeout, onTimeout: () => null); +} + Future _downloadImage(String imageUrl) { final Image networkImage = Image.network(imageUrl); final Completer completer = Completer(); @@ -21,10 +71,14 @@ Future _downloadImage(String imageUrl) { stream.addListener( ImageStreamListener( (ImageInfo image, bool synchronousCall) { - completer.complete(networkImage); + if (!completer.isCompleted) { + completer.complete(networkImage); + } }, onError: (Object exception, StackTrace? stackTrace) { - completer.complete(null); + if (!completer.isCompleted) { + completer.complete(null); + } }, ), );