Skip to content

Commit 9316aac

Browse files
committed
content [nfc]: Add ImageThumbnailLocator, wrapping thumbnail URLs
Soon we'll add a field on this for the `data-animated` attribute, then write some logic to help UI code choose a thumbnail format from RealmStore.serverThumbnailFormats.
1 parent 6342a22 commit 9316aac

File tree

4 files changed

+85
-48
lines changed

4 files changed

+85
-48
lines changed

lib/model/content.dart

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ class ImagePreviewNode extends BlockContentNode {
539539
const ImagePreviewNode({
540540
super.debugHtmlNode,
541541
required this.srcUrl,
542-
required this.thumbnailUrl,
542+
required this.thumbnail,
543543
required this.loading,
544544
required this.originalWidth,
545545
required this.originalHeight,
@@ -553,13 +553,12 @@ class ImagePreviewNode extends BlockContentNode {
553553

554554
/// The thumbnail URL of the image.
555555
///
556-
/// This may be a relative URL string. It also may not work without adding
557-
/// authentication credentials to the request.
556+
/// [ImageThumbnailLocator.urlPath] is a relative URL string.
558557
///
559558
/// This will be null if the server hasn't yet generated a thumbnail,
560559
/// or is a version that doesn't offer thumbnails.
561560
/// It will also be null when [loading] is true.
562-
final String? thumbnailUrl;
561+
final ImageThumbnailLocator? thumbnail;
563562

564563
/// A flag to indicate whether to show the placeholder.
565564
///
@@ -576,27 +575,58 @@ class ImagePreviewNode extends BlockContentNode {
576575
bool operator ==(Object other) {
577576
return other is ImagePreviewNode
578577
&& other.srcUrl == srcUrl
579-
&& other.thumbnailUrl == thumbnailUrl
578+
&& other.thumbnail == thumbnail
580579
&& other.loading == loading
581580
&& other.originalWidth == originalWidth
582581
&& other.originalHeight == originalHeight;
583582
}
584583

585584
@override
586585
int get hashCode => Object.hash('ImagePreviewNode',
587-
srcUrl, thumbnailUrl, loading, originalWidth, originalHeight);
586+
srcUrl, thumbnail, loading, originalWidth, originalHeight);
588587

589588
@override
590589
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
591590
super.debugFillProperties(properties);
592591
properties.add(StringProperty('srcUrl', srcUrl));
593-
properties.add(StringProperty('thumbnailUrl', thumbnailUrl));
592+
properties.add(DiagnosticsProperty<ImageThumbnailLocator>('thumbnail', thumbnail));
594593
properties.add(FlagProperty('loading', value: loading, ifTrue: "is loading"));
595594
properties.add(DoubleProperty('originalWidth', originalWidth));
596595
properties.add(DoubleProperty('originalHeight', originalHeight));
597596
}
598597
}
599598

599+
/// Data to locate an image thumbnail.
600+
@immutable
601+
class ImageThumbnailLocator extends DiagnosticableTree {
602+
ImageThumbnailLocator({
603+
required this.urlPath,
604+
}) : assert(urlPath.startsWith(urlPathPrefix));
605+
606+
/// The relative URL string for the default format,
607+
/// starting with [urlPathPrefix].
608+
///
609+
/// It may not work without adding authentication credentials to the request.
610+
final String urlPath;
611+
612+
static const urlPathPrefix = '/user_uploads/thumbnail/';
613+
614+
@override
615+
bool operator ==(Object other) {
616+
if (other is! ImageThumbnailLocator) return false;
617+
return urlPath == other.urlPath;
618+
}
619+
620+
@override
621+
int get hashCode => Object.hash('ImageThumbnailLocator', urlPath);
622+
623+
@override
624+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
625+
super.debugFillProperties(properties);
626+
properties.add(StringProperty('urlPath', urlPath));
627+
}
628+
}
629+
600630
class InlineVideoNode extends BlockContentNode {
601631
const InlineVideoNode({
602632
super.debugHtmlNode,
@@ -1399,7 +1429,7 @@ class _ZulipContentParser {
13991429
if (imgElement.className == 'image-loading-placeholder') {
14001430
return ImagePreviewNode(
14011431
srcUrl: href,
1402-
thumbnailUrl: null,
1432+
thumbnail: null,
14031433
loading: true,
14041434
originalWidth: null,
14051435
originalHeight: null,
@@ -1411,19 +1441,19 @@ class _ZulipContentParser {
14111441
}
14121442

14131443
final String srcUrl;
1414-
final String? thumbnailUrl;
1415-
if (src.startsWith('/user_uploads/thumbnail/')) {
1444+
final ImageThumbnailLocator? thumbnail;
1445+
if (src.startsWith(ImageThumbnailLocator.urlPathPrefix)) {
14161446
// For why we recognize this as the thumbnail form, see discussion:
14171447
// https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/documenting.20inline.20images/near/2279872
14181448
srcUrl = href;
1419-
thumbnailUrl = src;
1449+
thumbnail = ImageThumbnailLocator(urlPath: src);
14201450
} else {
14211451
// Known cases this handles:
14221452
// - `src` starts with CAMO_URI, a server variable (e.g. on Zulip Cloud
14231453
// it's "https://uploads.zulipusercontent.net/" in 2025-10).
14241454
// - `src` matches `href`, e.g. from pre-thumbnailing servers.
14251455
srcUrl = src;
1426-
thumbnailUrl = null;
1456+
thumbnail = null;
14271457
}
14281458

14291459
double? originalWidth, originalHeight;
@@ -1447,7 +1477,7 @@ class _ZulipContentParser {
14471477

14481478
return ImagePreviewNode(
14491479
srcUrl: srcUrl,
1450-
thumbnailUrl: thumbnailUrl,
1480+
thumbnail: thumbnail,
14511481
loading: false,
14521482
originalWidth: originalWidth,
14531483
originalHeight: originalHeight,

lib/widgets/content.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ class MessageImagePreview extends StatelessWidget {
637637

638638
// TODO image hover animation
639639
final srcUrl = node.srcUrl;
640-
final thumbnailUrl = node.thumbnailUrl;
640+
final thumbnailUrl = node.thumbnail?.urlPath;
641641
final store = PerAccountStoreWidget.of(context);
642642
final resolvedSrcUrl = store.tryResolveUrl(srcUrl);
643643
final resolvedThumbnailUrl = thumbnailUrl == null

test/model/content_test.dart

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ class ContentExample {
366366
]),
367367
ImagePreviewNodeList([
368368
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png?version=3',
369-
thumbnailUrl: null, loading: false,
369+
thumbnail: null, loading: false,
370370
originalWidth: null, originalHeight: null),
371371
]),
372372
],
@@ -701,7 +701,7 @@ class ContentExample {
701701
ImagePreviewNodeList([
702702
ImagePreviewNode(
703703
srcUrl: '/external_content/de28eb3abf4b7786de4545023dc42d434a2ea0c2/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
704-
thumbnailUrl: null,
704+
thumbnail: null,
705705
loading: false,
706706
originalWidth: null,
707707
originalHeight: null),
@@ -719,14 +719,14 @@ class ContentExample {
719719
ImagePreviewNodeList([
720720
ImagePreviewNode(
721721
srcUrl: '/external_content/58b0ef9a06d7bb24faec2b11df2f57f476e6f6bb/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f7468756d622f372f37312f5a616164706c75697a656e5f76616e5f65656e5f436c656d617469735f746578656e7369735f2532375072696e636573735f4469616e612532372e5f31382d30372d323032335f2532386163746d2e2532395f30322e6a70672f3132383070782d5a616164706c75697a656e5f76616e5f65656e5f436c656d617469735f746578656e7369735f2532375072696e636573735f4469616e612532372e5f31382d30372d323032335f2532386163746d2e2532395f30322e6a7067',
722-
thumbnailUrl: null,
722+
thumbnail: null,
723723
loading: false,
724724
originalWidth: null,
725725
originalHeight: null),
726726
]),
727727
]);
728728

729-
static const imagePreviewSingle = ContentExample(
729+
static final imagePreviewSingle = ContentExample(
730730
'single image preview',
731731
// https://chat.zulip.org/#narrow/stream/7-test-here/topic/Thumbnails/near/1900103
732732
"[image.jpg](/user_uploads/2/ce/nvoNL2LaZOciwGZ-FYagddtK/image.jpg)",
@@ -735,14 +735,15 @@ class ContentExample {
735735
'<img data-original-dimensions="6000x4000" src="/user_uploads/thumbnail/2/ce/nvoNL2LaZOciwGZ-FYagddtK/image.jpg/840x560.webp"></a></div>', [
736736
ImagePreviewNodeList([
737737
ImagePreviewNode(srcUrl: '/user_uploads/2/ce/nvoNL2LaZOciwGZ-FYagddtK/image.jpg',
738-
thumbnailUrl: '/user_uploads/thumbnail/2/ce/nvoNL2LaZOciwGZ-FYagddtK/image.jpg/840x560.webp',
738+
thumbnail: ImageThumbnailLocator(
739+
urlPath: '/user_uploads/thumbnail/2/ce/nvoNL2LaZOciwGZ-FYagddtK/image.jpg/840x560.webp'),
739740
loading: false,
740741
originalWidth: 6000,
741742
originalHeight: 4000),
742743
]),
743744
]);
744745

745-
static const imagePreviewSingleNoDimensions = ContentExample(
746+
static final imagePreviewSingleNoDimensions = ContentExample(
746747
'single image preview no dimensions',
747748
// https://chat.zulip.org/#narrow/stream/7-test-here/topic/Thumbnails/near/1893590
748749
"[image.jpg](/user_uploads/2/c3/wb9FXk8Ej6qIc28aWKcqUogD/image.jpg)",
@@ -751,7 +752,8 @@ class ContentExample {
751752
'<img src="/user_uploads/thumbnail/2/c3/wb9FXk8Ej6qIc28aWKcqUogD/image.jpg/840x560.webp"/></a></div>', [
752753
ImagePreviewNodeList([
753754
ImagePreviewNode(srcUrl: '/user_uploads/2/c3/wb9FXk8Ej6qIc28aWKcqUogD/image.jpg',
754-
thumbnailUrl: '/user_uploads/thumbnail/2/c3/wb9FXk8Ej6qIc28aWKcqUogD/image.jpg/840x560.webp',
755+
thumbnail: ImageThumbnailLocator(
756+
urlPath: '/user_uploads/thumbnail/2/c3/wb9FXk8Ej6qIc28aWKcqUogD/image.jpg/840x560.webp'),
755757
loading: false,
756758
originalWidth: null,
757759
originalHeight: null),
@@ -766,7 +768,7 @@ class ContentExample {
766768
'<img src="https://chat.zulip.org/user_avatars/2/realm/icon.png?version=3"></a></div>', [
767769
ImagePreviewNodeList([
768770
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png?version=3',
769-
thumbnailUrl: null, loading: false,
771+
thumbnail: null, loading: false,
770772
originalWidth: null, originalHeight: null),
771773
]),
772774
]);
@@ -780,7 +782,7 @@ class ContentExample {
780782
'<img class="image-loading-placeholder" src="/static/images/loading/loader-black.svg"></a></div>', [
781783
ImagePreviewNodeList([
782784
ImagePreviewNode(srcUrl: '/user_uploads/2/c3/wb9FXk8Ej6qIc28aWKcqUogD/image.jpg',
783-
thumbnailUrl: null, loading: true,
785+
thumbnail: null, loading: true,
784786
originalWidth: null, originalHeight: null),
785787
]),
786788
]);
@@ -794,7 +796,7 @@ class ContentExample {
794796
'<img src="/external_content/de28eb3abf4b7786de4545023dc42d434a2ea0c2/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067"></a></div>', [
795797
ImagePreviewNodeList([
796798
ImagePreviewNode(srcUrl: '/external_content/de28eb3abf4b7786de4545023dc42d434a2ea0c2/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
797-
thumbnailUrl: null, loading: false,
799+
thumbnail: null, loading: false,
798800
originalWidth: null, originalHeight: null),
799801
]),
800802
]);
@@ -809,7 +811,7 @@ class ContentExample {
809811
'<img src="https://uploads.zulipusercontent.net/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067"></a></div>', [
810812
ImagePreviewNodeList([
811813
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
812-
thumbnailUrl: null, loading: false,
814+
thumbnail: null, loading: false,
813815
originalWidth: null, originalHeight: null),
814816
]),
815817
]);
@@ -824,7 +826,7 @@ class ContentExample {
824826
'<img src="https://custom.camo-uri.example/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067"></a></div>', [
825827
ImagePreviewNodeList([
826828
ImagePreviewNode(srcUrl: 'https://custom.camo-uri.example/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
827-
thumbnailUrl: null, loading: false,
829+
thumbnail: null, loading: false,
828830
originalWidth: null, originalHeight: null),
829831
]),
830832
]);
@@ -837,12 +839,12 @@ class ContentExample {
837839
'<img src="::not a URL::"></a></div>', [
838840
ImagePreviewNodeList([
839841
ImagePreviewNode(srcUrl: '::not a URL::',
840-
thumbnailUrl: null, loading: false,
842+
thumbnail: null, loading: false,
841843
originalWidth: null, originalHeight: null),
842844
]),
843845
]);
844846

845-
static const imagePreviewCluster = ContentExample(
847+
static final imagePreviewCluster = ContentExample(
846848
'multiple image previews',
847849
// https://chat.zulip.org/#narrow/stream/7-test-here/topic/Thumbnails/near/1893154
848850
"[image.jpg](/user_uploads/2/9b/WkDt2Qsy79iwf3sM9EMp9fYL/image.jpg)\n[image2.jpg](/user_uploads/2/70/pVeI52TwFUEoFE2qT_u9AMCO/image2.jpg)",
@@ -863,12 +865,14 @@ class ContentExample {
863865
]),
864866
ImagePreviewNodeList([
865867
ImagePreviewNode(srcUrl: '/user_uploads/2/9b/WkDt2Qsy79iwf3sM9EMp9fYL/image.jpg',
866-
thumbnailUrl: '/user_uploads/thumbnail/2/9b/WkDt2Qsy79iwf3sM9EMp9fYL/image.jpg/840x560.webp',
868+
thumbnail: ImageThumbnailLocator(
869+
urlPath: '/user_uploads/thumbnail/2/9b/WkDt2Qsy79iwf3sM9EMp9fYL/image.jpg/840x560.webp'),
867870
loading: false,
868871
originalWidth: null,
869872
originalHeight: null),
870873
ImagePreviewNode(srcUrl: '/user_uploads/2/70/pVeI52TwFUEoFE2qT_u9AMCO/image2.jpg',
871-
thumbnailUrl: '/user_uploads/thumbnail/2/70/pVeI52TwFUEoFE2qT_u9AMCO/image2.jpg/840x560.webp',
874+
thumbnail: ImageThumbnailLocator(
875+
urlPath: '/user_uploads/thumbnail/2/70/pVeI52TwFUEoFE2qT_u9AMCO/image2.jpg/840x560.webp'),
872876
loading: false,
873877
originalWidth: null,
874878
originalHeight: null),
@@ -895,10 +899,10 @@ class ContentExample {
895899
]),
896900
ImagePreviewNodeList([
897901
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/f535ba07f95b99a83aa48e44fd62bbb6c6cf6615/68747470733a2f2f636861742e7a756c69702e6f72672f757365725f617661746172732f322f7265616c6d2f69636f6e2e706e673f76657273696f6e3d33',
898-
thumbnailUrl: null, loading: false,
902+
thumbnail: null, loading: false,
899903
originalWidth: null, originalHeight: null),
900904
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/8f63bc2632a0e41be3f457d86c077e61b4a03e7e/68747470733a2f2f636861742e7a756c69702e6f72672f757365725f617661746172732f322f7265616c6d2f69636f6e2e706e673f76657273696f6e3d34',
901-
thumbnailUrl: null, loading: false,
905+
thumbnail: null, loading: false,
902906
originalWidth: null, originalHeight: null),
903907
]),
904908
]);
@@ -924,10 +928,10 @@ class ContentExample {
924928
]),
925929
ImagePreviewNodeList([
926930
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png',
927-
thumbnailUrl: null, loading: false,
931+
thumbnail: null, loading: false,
928932
originalWidth: null, originalHeight: null),
929933
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png?version=2',
930-
thumbnailUrl: null, loading: false,
934+
thumbnail: null, loading: false,
931935
originalWidth: null, originalHeight: null),
932936
]),
933937
ParagraphNode(links: null, nodes: [
@@ -964,10 +968,10 @@ class ContentExample {
964968
]),
965969
ImagePreviewNodeList([
966970
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/34b2695ca83af76204b0b25a8f2019ee35ec38fa/68747470733a2f2f656e2e77696b6970656469612e6f72672f7374617469632f696d616765732f69636f6e732f77696b6970656469612e706e67',
967-
thumbnailUrl: null, loading: false,
971+
thumbnail: null, loading: false,
968972
originalWidth: null, originalHeight: null),
969973
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/d200fb112aaccbff9df767373a201fa59601f362/68747470733a2f2f656e2e77696b6970656469612e6f72672f7374617469632f696d616765732f69636f6e732f77696b6970656469612e706e673f763d31',
970-
thumbnailUrl: null, loading: false,
974+
thumbnail: null, loading: false,
971975
originalWidth: null, originalHeight: null),
972976
]),
973977
ParagraphNode(links: null, nodes: [
@@ -981,10 +985,10 @@ class ContentExample {
981985
]),
982986
ImagePreviewNodeList([
983987
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/c4db87e81348dac94eacaa966b46d968b34029cc/68747470733a2f2f656e2e77696b6970656469612e6f72672f7374617469632f696d616765732f69636f6e732f77696b6970656469612e706e673f763d32',
984-
thumbnailUrl: null, loading: false,
988+
thumbnail: null, loading: false,
985989
originalWidth: null, originalHeight: null),
986990
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/51b70540cf6a5b3c8a0b919c893b8abddd447e88/68747470733a2f2f656e2e77696b6970656469612e6f72672f7374617469632f696d616765732f69636f6e732f77696b6970656469612e706e673f763d33',
987-
thumbnailUrl: null, loading: false,
991+
thumbnail: null, loading: false,
988992
originalWidth: null, originalHeight: null),
989993
]),
990994
]);
@@ -1000,7 +1004,7 @@ class ContentExample {
10001004
UnorderedListNode([[
10011005
ImagePreviewNodeList([
10021006
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png',
1003-
thumbnailUrl: null, loading: false,
1007+
thumbnail: null, loading: false,
10041008
originalWidth: null, originalHeight: null),
10051009
]),
10061010
]]),
@@ -1027,10 +1031,10 @@ class ContentExample {
10271031
]),
10281032
ImagePreviewNodeList([
10291033
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png',
1030-
thumbnailUrl: null, loading: false,
1034+
thumbnail: null, loading: false,
10311035
originalWidth: null, originalHeight: null),
10321036
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png?version=2',
1033-
thumbnailUrl: null, loading: false,
1037+
thumbnail: null, loading: false,
10341038
originalWidth: null, originalHeight: null),
10351039
]),
10361040
]]),
@@ -1055,7 +1059,7 @@ class ContentExample {
10551059
]),
10561060
const ImagePreviewNodeList([
10571061
ImagePreviewNode(srcUrl: 'https://chat.zulip.org/user_avatars/2/realm/icon.png',
1058-
thumbnailUrl: null, loading: false,
1062+
thumbnail: null, loading: false,
10591063
originalWidth: null, originalHeight: null),
10601064
]),
10611065
blockUnimplemented('more text'),
@@ -1404,7 +1408,7 @@ class ContentExample {
14041408
]),
14051409
]);
14061410

1407-
static const tableWithImagePreview = ContentExample(
1411+
static final tableWithImagePreview = ContentExample(
14081412
'table with image preview',
14091413
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/.E2.9C.94.20Rajesh/near/1987666
14101414
'| a |\n| - |\n| [image2.jpg](/user_uploads/2/6f/KS3vNT9c2tbMfMBkSbQF_Jlj/image2.jpg) |',
@@ -1421,7 +1425,8 @@ class ContentExample {
14211425
]),
14221426
ImagePreviewNodeList([
14231427
ImagePreviewNode(srcUrl: '/user_uploads/2/6f/KS3vNT9c2tbMfMBkSbQF_Jlj/image2.jpg',
1424-
thumbnailUrl: '/user_uploads/thumbnail/2/6f/KS3vNT9c2tbMfMBkSbQF_Jlj/image2.jpg/840x560.webp',
1428+
thumbnail: ImageThumbnailLocator(
1429+
urlPath: '/user_uploads/thumbnail/2/6f/KS3vNT9c2tbMfMBkSbQF_Jlj/image2.jpg/840x560.webp'),
14251430
loading: false,
14261431
originalWidth: 2760,
14271432
originalHeight: 4912),

0 commit comments

Comments
 (0)