Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 101 additions & 122 deletions lib/widgets/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -633,61 +633,10 @@ class MessageImagePreview extends StatelessWidget {

@override
Widget build(BuildContext context) {
final store = PerAccountStoreWidget.of(context);
final message = InheritedMessage.of(context);

final resolvedSrc = switch (node.src) {
ImageNodeSrcThumbnail(:final value) => value.resolve(context,
width: MessageMediaContainer.width,
height: MessageMediaContainer.height,
animationMode: .animateConditionally),
ImageNodeSrcOther(:final value) => store.tryResolveUrl(value),
};
final originalSrc = node.originalSrc;
final resolvedOriginalSrc = originalSrc == null ? null
: store.tryResolveUrl(originalSrc);

final child = switch ((node.loading, resolvedSrc)) {
// resolvedSrc would be a "spinner" image URL.
// Use our own progress indicator instead.
(true, _) => const CupertinoActivityIndicator(),

// TODO(#265) use an error-case placeholder
// TODO(log)
(false, null) => null,

(false, Uri()) => RealmContentNetworkImage(
// TODO(#265) use an error-case placeholder for `errorBuilder`
filterQuality: FilterQuality.medium,
resolvedSrc!),
};

final lightboxDisplayUrl = (node.loading || node.src is ImageNodeSrcThumbnail)
? resolvedOriginalSrc
: resolvedSrc;
if (lightboxDisplayUrl == null) {
// TODO(log)
return MessageMediaContainer(onTap: null, child: child);
}

return MessageMediaContainer(
onTap: () {
Navigator.of(context).push(getImageLightboxRoute(
context: context,
message: message,
messageImageContext: context,
src: lightboxDisplayUrl,
thumbnailUrl: (node.src is ImageNodeSrcThumbnail && !node.loading)
? resolvedSrc
: null,
originalWidth: node.originalWidth,
originalHeight: node.originalHeight));
},
child: child == null ? null :
LightboxHero(
messageImageContext: context,
src: lightboxDisplayUrl,
child: child));
return _Image(node: node, size: MessageMediaContainer.size,
buildContainer: (onTap, child) {
return MessageMediaContainer(onTap: onTap, child: child);
});
}
}

Expand Down Expand Up @@ -760,11 +709,8 @@ class MessageMediaContainer extends StatelessWidget {
final void Function()? onTap;
final Widget? child;

/// The container's width, in logical pixels.
static const width = 150.0;

/// The container's height, in logical pixels.
static const height = 100.0;
/// The container's size, in logical pixels.
static const size = Size(150, 100);

@override
Widget build(BuildContext context) {
Expand All @@ -780,9 +726,8 @@ class MessageMediaContainer extends StatelessWidget {
color: ContentTheme.of(context).colorMessageMediaContainerBackground,
child: Padding(
padding: const EdgeInsets.all(1),
child: SizedBox(
width: width,
height: height,
child: SizedBox.fromSize(
size: size,
child: child))))));
}
}
Expand Down Expand Up @@ -1334,55 +1279,6 @@ class InlineImage extends StatelessWidget {
final InlineImageNode node;
final TextStyle ambientTextStyle;

Widget _buildContent(BuildContext context, {required Size size}) {
final store = PerAccountStoreWidget.of(context);

if (node.loading) {
return CupertinoActivityIndicator();
}

final src = node.src;
final resolvedSrc = switch (src) {
ImageNodeSrcThumbnail() => src.value.resolve(context,
width: size.width,
height: size.height,
animationMode: .animateConditionally),
ImageNodeSrcOther() => store.tryResolveUrl(src.value),
};
if (resolvedSrc == null) {
// TODO(#265) use an error-case placeholder here
return SizedBox.shrink();
}

Widget result;
result = RealmContentNetworkImage(
// TODO(#265) use an error-case placeholder for `errorBuilder`
semanticLabel: node.alt,
resolvedSrc);

final resolvedOriginalSrc = node.originalSrc == null ? null
: store.tryResolveUrl(node.originalSrc!);
if (resolvedOriginalSrc != null) {
result = GestureDetector(
onTap: () {
Navigator.of(context).push(getImageLightboxRoute(
context: context,
message: InheritedMessage.of(context),
messageImageContext: context,
src: resolvedOriginalSrc,
thumbnailUrl: resolvedSrc,
originalWidth: node.originalWidth,
originalHeight: node.originalHeight));
},
child: LightboxHero(
messageImageContext: context,
src: resolvedOriginalSrc,
child: result));
}

return result;
}

@override
Widget build(BuildContext context) {
final devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
Expand All @@ -1395,7 +1291,7 @@ class InlineImage extends StatelessWidget {
? Size(node.originalWidth!, node.originalHeight!) / devicePixelRatio
// Layout plan when original dimensions are unknown:
// a [MessageMediaContainer]-sized and -colored rectangle.
: Size(MessageMediaContainer.width, MessageMediaContainer.height);
: MessageMediaContainer.size;

// (a) Don't let tall, thin images take up too much vertical space,
// which could be annoying to scroll through. And:
Expand All @@ -1408,15 +1304,11 @@ class InlineImage extends StatelessWidget {
final size = BoxConstraints(maxHeight: maxHeight)
.constrainSizeAndAttemptToPreserveAspectRatio(imageSize);

Widget child = _buildContent(context, size: size);
if (node.alt != null) {
child = Tooltip(
message: node.alt,
// (Instead of setting a semantics label here,
// we give the alt text to [RealmContentNetworkImage].)
excludeFromSemantics: true,
child: child);
}
Widget child = _Image(node: node, size: size,
buildContainer: (onTap, child) {
if (onTap == null) return child;
return GestureDetector(onTap: onTap, child: child);
});

return Padding(
// Separate images vertically when they flow onto separate lines.
Expand Down Expand Up @@ -1558,6 +1450,93 @@ class MessageTableCell extends StatelessWidget {
}
}

typedef _ImageContainerBuilder = Widget Function(VoidCallback? onTap, Widget child);

/// A helper widget to deduplicate much of the logic in common
/// between image previews and inline images.
class _Image extends StatelessWidget {
const _Image({
required this.node,
required this.size,
required this.buildContainer,
});

final ImageNode node;
final Size size;
final _ImageContainerBuilder buildContainer;

@override
Widget build(BuildContext context) {
final store = PerAccountStoreWidget.of(context);
final message = InheritedMessage.of(context);

final resolvedSrc = switch (node.src) {
ImageNodeSrcThumbnail(:final value) => value.resolve(context,
width: size.width,
height: size.height,
animationMode: .animateConditionally),
ImageNodeSrcOther(:final value) => store.tryResolveUrl(value),
};
final resolvedOriginalSrc = node.originalSrc == null ? null
: store.tryResolveUrl(node.originalSrc!);

Widget child = switch ((node.loading, resolvedSrc)) {
// resolvedSrc would be a "spinner" image URL.
// Use our own progress indicator instead.
(true, _) => const CupertinoActivityIndicator(),

// TODO(#265) use an error-case placeholder
// TODO(log)
(false, null) => SizedBox.shrink(),

(false, Uri()) => RealmContentNetworkImage(
// TODO(#265) use an error-case placeholder for `errorBuilder`
filterQuality: FilterQuality.medium,
semanticLabel: node.alt,
resolvedSrc!),
};

if (node.alt != null) {
child = Tooltip(
message: node.alt,
// (Instead of setting a semantics label here,
// we give the alt text to [RealmContentNetworkImage].)
excludeFromSemantics: true,
child: child);
}

final lightboxDisplayUrl = (node.loading || node.src is ImageNodeSrcThumbnail)
? resolvedOriginalSrc
: resolvedSrc;
if (lightboxDisplayUrl == null) {
// TODO(log)
return buildContainer(null, child);
}

return buildContainer(
() {
Navigator.of(context).push(getImageLightboxRoute(
context: context,
message: message,
messageImageContext: context,
src: lightboxDisplayUrl,
thumbnailUrl: node.src is ImageNodeSrcThumbnail
? node.loading
// (Image thumbnail is loading; don't show hard-coded spinner image
// even if that happens to be a thumbnail URL.)
? null
: resolvedSrc
: null,
originalWidth: node.originalWidth,
originalHeight: node.originalHeight));
},
LightboxHero(
messageImageContext: context,
src: lightboxDisplayUrl,
child: child));
}
}

void _launchUrl(BuildContext context, String urlString) async {
final store = PerAccountStoreWidget.of(context);
final url = store.tryResolveUrl(urlString);
Expand Down