From 48790cb3343d84784410fd06595324b389158a8e Mon Sep 17 00:00:00 2001 From: ababak Date: Thu, 20 Aug 2026 11:39:52 +0300 Subject: [PATCH 1/5] fix: display dashboard images configured via external URL link --- lib/utils/utils.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 9af56b7e..0665c6a2 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -115,20 +115,20 @@ abstract class Utils { semanticLabel: semanticLabel, onError: onError, ); - } else if (_isBase64DataImageUrl(imageUrl)) { + } else if (_isBase64DataImageUrl(newImageUrl)) { return _imageFromBase64( context, - imageUrl, + newImageUrl, color: color, width: width, height: height, semanticLabel: semanticLabel, onError: onError, ); - } else if (_isValidUrl(imageUrl)) { + } else if (_isValidUrl(newImageUrl)) { return _networkImage( context, - imageUrl, + newImageUrl, color: color, width: width, height: height, From c7a029203ee8506cbc322612d449a657762d9bbf Mon Sep 17 00:00:00 2001 From: ababak Date: Fri, 21 Aug 2026 15:42:43 +0300 Subject: [PATCH 2/5] fix: resolve dashboard image links relative to the active endpoint Address review feedback on PR #249: - use the prefix-stripped url consistently and rename it to resolvedImageUrl, matching ImageService.resolveImageUrl on the web - anchor the tb-image prefix strip so an occurrence inside a link is kept - replace _isValidUrl, which accepted almost any string, with _resolveNetworkImageLink: absolute http/https links are fetched as they are, platform-relative links such as an image public link are resolved against the active endpoint the way a browser resolves them against its origin, and anything else renders the missing image --- lib/utils/utils.dart | 56 +++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0665c6a2..41735505 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -85,8 +85,8 @@ abstract class Utils { onError: onError, ); } else { - final newImageUrl = _removeTbImagePrefix(imageUrl); - if (_isImageResourceUrl(newImageUrl)) { + final resolvedImageUrl = _removeTbImagePrefix(imageUrl); + if (_isImageResourceUrl(resolvedImageUrl)) { final jwtToken = tbClient.getJwtToken(); if (jwtToken == null) { return _onErrorImage( @@ -98,7 +98,7 @@ abstract class Utils { onError: onError, ); } - final parts = newImageUrl.split('/'); + final parts = resolvedImageUrl.split('/'); final key = parts[parts.length - 1]; parts[parts.length - 1] = Uri.encodeComponent(key); final encodedUrl = parts.join('/'); @@ -115,20 +115,10 @@ abstract class Utils { semanticLabel: semanticLabel, onError: onError, ); - } else if (_isBase64DataImageUrl(newImageUrl)) { + } else if (_isBase64DataImageUrl(resolvedImageUrl)) { return _imageFromBase64( context, - newImageUrl, - color: color, - width: width, - height: height, - semanticLabel: semanticLabel, - onError: onError, - ); - } else if (_isValidUrl(newImageUrl)) { - return _networkImage( - context, - newImageUrl, + resolvedImageUrl, color: color, width: width, height: height, @@ -136,8 +126,20 @@ abstract class Utils { onError: onError, ); } else { - return _onErrorImage( + final imageLink = _resolveNetworkImageLink(resolvedImageUrl); + if (imageLink == null) { + return _onErrorImage( + context, + color: color, + width: width, + height: height, + semanticLabel: semanticLabel, + onError: onError, + ); + } + return _networkImage( context, + imageLink, color: color, width: width, height: height, @@ -287,15 +289,31 @@ abstract class Utils { } static String _removeTbImagePrefix(String url) { - return url.replaceFirst(_tbImagePrefix, ''); + return url.startsWith(_tbImagePrefix) + ? url.substring(_tbImagePrefix.length) + : url; } static bool _isImageResourceUrl(String url) { return _imagesUrlRegexp.hasMatch(url); } - static bool _isValidUrl(String url) { - return Uri.tryParse(url) != null; + /// Absolute links are fetched as they are. Links relative to the platform, + /// such as an image public link, are resolved against the active endpoint the + /// same way a browser resolves them against its origin. Anything else has no + /// meaningful target and is rendered as a missing image. + static String? _resolveNetworkImageLink(String url) { + final uri = Uri.tryParse(url); + if (uri == null) { + return null; + } + if (uri.scheme == 'http' || uri.scheme == 'https') { + return url; + } + if (!uri.hasScheme && url.startsWith('/')) { + return getIt().getCachedEndpoint() + url; + } + return null; } static double degreesToRadians(double degrees) { From 1eb4637b1f93d95169da1e23176a48685a10f04b Mon Sep 17 00:00:00 2001 From: ababak Date: Fri, 4 Sep 2026 13:53:51 +0300 Subject: [PATCH 3/5] docs: clarify HTTP(S)-only wording on image link resolver --- lib/utils/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 41735505..9dec8d24 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -298,7 +298,7 @@ abstract class Utils { return _imagesUrlRegexp.hasMatch(url); } - /// Absolute links are fetched as they are. Links relative to the platform, + /// HTTP(S) links are fetched as they are. Links relative to the platform, /// such as an image public link, are resolved against the active endpoint the /// same way a browser resolves them against its origin. Anything else has no /// meaningful target and is rendered as a missing image. From 23eb34ede03757ccda32c4bf8485bec0fea99d76 Mon Sep 17 00:00:00 2001 From: ababak Date: Fri, 4 Sep 2026 16:37:38 +0300 Subject: [PATCH 4/5] docs: drop inaccurate browser-resolution clause from image link resolver --- lib/utils/utils.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 9dec8d24..0fcb21cf 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -299,9 +299,9 @@ abstract class Utils { } /// HTTP(S) links are fetched as they are. Links relative to the platform, - /// such as an image public link, are resolved against the active endpoint the - /// same way a browser resolves them against its origin. Anything else has no - /// meaningful target and is rendered as a missing image. + /// such as an image public link, are resolved against the active endpoint. + /// Anything else has no meaningful target and is rendered as a missing + /// image. static String? _resolveNetworkImageLink(String url) { final uri = Uri.tryParse(url); if (uri == null) { From 6e4f884d5b359b5c442c48998e71e7845f88292a Mon Sep 17 00:00:00 2001 From: ababak Date: Mon, 7 Sep 2026 10:35:11 +0300 Subject: [PATCH 5/5] docs: say root-relative in image link resolver comment --- lib/utils/utils.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0fcb21cf..99f778f4 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -298,10 +298,9 @@ abstract class Utils { return _imagesUrlRegexp.hasMatch(url); } - /// HTTP(S) links are fetched as they are. Links relative to the platform, - /// such as an image public link, are resolved against the active endpoint. - /// Anything else has no meaningful target and is rendered as a missing - /// image. + /// HTTP(S) links are fetched as they are. Root-relative links, such as an + /// image public link, are resolved against the active endpoint. Anything + /// else has no meaningful target and is rendered as a missing image. static String? _resolveNetworkImageLink(String url) { final uri = Uri.tryParse(url); if (uri == null) {