Skip to content

Commit

Permalink
Support SVG previews in markdown (thunder-app#938)
Browse files Browse the repository at this point in the history
* Support SVG previews in markdown

* Minor fixes to SVG instantiation
  • Loading branch information
micahmo authored Dec 4, 2023
1 parent 235b368 commit 8407e60
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Added setting to show comment score instead of upvote/downvote counts
- Added setting to show/hide post and comment scores
- Added setting to show/hide bot content
- Added the ability to render SVGs in markdown bodies - contribution from @micahmo

### Fixed
- Fixed issue where custom tabs would not respect default browser when opening links
Expand Down
8 changes: 8 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,14 @@
},
"unableToFindUser": "Unable to find user",
"@unableToFindUser": {},
"unableToLoadImage": "Unable to load image",
"@unableToLoadImage": {
"description": "Placeholder for when we are unable to load a binary image"
},
"unableToLoadImageFrom": "Unable to load image from {domain}",
"@unableToLoadImageFrom": {
"description": "Placeholder for when we are unable to load an image from a URL"
},
"unableToLoadInstance": "Unable to load {instance}",
"@unableToLoadInstance": {},
"unableToLoadPostsFrominstance": "Unable to load posts from {instance}",
Expand Down
37 changes: 25 additions & 12 deletions lib/shared/common_markdown_body.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:jovial_svg/jovial_svg.dart';

import 'package:markdown/markdown.dart' as md;
import 'package:flutter_bloc/flutter_bloc.dart';
Expand All @@ -11,6 +12,7 @@ import 'package:link_preview_generator/link_preview_generator.dart';
import 'package:thunder/core/enums/font_scale.dart';
import 'package:thunder/shared/image_preview.dart';
import 'package:thunder/utils/bottom_sheet_list_picker.dart';
import 'package:thunder/utils/image.dart';
import 'package:thunder/utils/links.dart';
import 'package:thunder/thunder/bloc/thunder_bloc.dart';
import 'package:thunder/utils/markdown/extended_markdown.dart';
Expand Down Expand Up @@ -43,19 +45,30 @@ class CommonMarkdownBody extends StatelessWidget {
data: body,
inlineSyntaxes: [LemmyLinkSyntax()],
imageBuilder: (uri, title, alt) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ImagePreview(
url: uri.toString(),
isExpandable: true,
isComment: isComment,
showFullHeightImages: true,
return FutureBuilder(
future: isImageUriSvg(uri),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
!snapshot.hasData
? Container()
: snapshot.data == true
? ScalableImageWidget.fromSISource(
si: ScalableImageSource.fromSvgHttpUrl(uri),
)
: ImagePreview(
url: uri.toString(),
isExpandable: true,
isComment: isComment,
showFullHeightImages: true,
),
],
),
],
),
);
},
);
},
selectable: isSelectableText,
Expand Down
18 changes: 18 additions & 0 deletions lib/shared/image_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:thunder/shared/image_viewer.dart';
import 'package:thunder/utils/image.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class ImagePreview extends StatefulWidget {
final String? url;
Expand Down Expand Up @@ -76,6 +77,9 @@ class _ImagePreviewState extends State<ImagePreview> {
}

Widget imagePreview(BuildContext context) {
final ThemeData theme = Theme.of(context);
final AppLocalizations l10n = AppLocalizations.of(context)!;

return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)),
Expand Down Expand Up @@ -108,6 +112,14 @@ class _ImagePreviewState extends State<ImagePreview> {
if (state.extendedImageLoadState == LoadState.loading) {
return Container();
}
if (state.extendedImageLoadState == LoadState.failed) {
return Text(
l10n.unableToLoadImageFrom(Uri.parse(widget.url!).host),
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.textTheme.bodyMedium?.color?.withOpacity(0.5),
),
);
}
},
)
: ExtendedImage.memory(
Expand All @@ -132,6 +144,12 @@ class _ImagePreviewState extends State<ImagePreview> {
if (state.extendedImageLoadState == LoadState.loading) {
return Container();
}
return Text(
l10n.unableToLoadImage,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.textTheme.bodyMedium?.color?.withOpacity(0.5),
),
);
},
),
TweenAnimationBuilder<double>(
Expand Down
22 changes: 22 additions & 0 deletions lib/utils/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ bool isImageUrl(String url) {
return false;
}

Future<bool> isImageUrlSvg(String imageUrl) async {
return isImageUriSvg(Uri.tryParse(imageUrl));
}

Future<bool> isImageUriSvg(Uri? imageUri) async {
try {
final http.Response response = await http.get(
imageUri ?? Uri(),
// Get the headers and ask for 0 bytes of the body
// to make this a lightweight request
headers: {
'method': 'HEAD',
'Range': 'bytes=0-0',
},
);
return response.headers['content-type']?.toLowerCase().contains('svg') == true;
} catch (e) {
// If it fails for any reason, it's not an SVG!
return false;
}
}

Future<Size> retrieveImageDimensions(String imageUrl) async {
try {
bool isImage = isImageUrl(imageUrl);
Expand Down
16 changes: 16 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
jovial_misc:
dependency: transitive
description:
name: jovial_misc
sha256: f6e64f789ee311025bb367be9c9afe9759f76dd8209070b7f38e735b5f529eb1
url: "https://pub.dev"
source: hosted
version: "0.8.5"
jovial_svg:
dependency: "direct main"
description:
name: jovial_svg
sha256: "917c63f774f3a9053777b55d824a60090ab87e65a7f6219ca1c478ccd0314f4a"
url: "https://pub.dev"
source: hosted
version: "1.1.19"
js:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ dependencies:
url: https://github.com/deakjahn/l10n_esperanto.git
ref: 78e757337a1de881220086d26a083feeb780f609
sqflite_common_ffi_web: ^0.4.2
jovial_svg: ^1.1.19

dev_dependencies:
build_runner: ^2.4.6
Expand Down

0 comments on commit 8407e60

Please sign in to comment.