Skip to content

Commit f3c125d

Browse files
committed
✨ added svg image support
1 parent 9b25bc6 commit f3c125d

File tree

4 files changed

+256
-3
lines changed

4 files changed

+256
-3
lines changed

lib/home.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import 'package:flutter/material.dart';
77
import 'package:flutter/services.dart';
88
import 'package:markdown_editor/device_preference_notifier.dart';
99
import 'package:markdown_editor/l10n/generated/app_localizations.dart';
10+
import 'package:markdown_editor/widgets/MarkdownBody/custom_image_config.dart';
1011
import 'package:markdown_editor/widgets/MarkdownTextInput/markdown_text_input.dart';
11-
import 'package:markdown_widget/config/configs.dart';
12-
import 'package:markdown_widget/widget/markdown_block.dart';
12+
import 'package:markdown_widget/markdown_widget.dart';
1313

1414
enum MenuItem { switchTheme, switchView, open, clear, save }
1515

@@ -224,7 +224,10 @@ class _HomeState extends State<Home> {
224224
child: SingleChildScrollView(
225225
controller: _scrollController,
226226
padding: const EdgeInsets.all(8),
227-
child: MarkdownBlock(data: _inputText, config: config),
227+
child: MarkdownBlock(
228+
data: _inputText,
229+
config: config.copy(configs: [CustomImgConfig()]),
230+
),
228231
),
229232
),
230233
),
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_svg/flutter_svg.dart';
3+
import 'package:markdown_widget/markdown_widget.dart';
4+
5+
class CustomImgConfig extends ImgConfig {
6+
final bool wrapWithViewer;
7+
8+
final BoxFit rasterFit;
9+
10+
final BoxFit svgFit;
11+
12+
final AlignmentGeometry alignment;
13+
14+
CustomImgConfig({
15+
this.wrapWithViewer = true,
16+
this.rasterFit = BoxFit.cover,
17+
this.svgFit = BoxFit.scaleDown,
18+
this.alignment = Alignment.center,
19+
super.errorBuilder,
20+
}) : super(
21+
builder: (url, attrs) {
22+
double? width;
23+
double? height;
24+
try {
25+
final w = attrs['width'];
26+
final h = attrs['height'];
27+
if (w != null && w.trim().isNotEmpty) width = double.parse(w);
28+
if (h != null && h.trim().isNotEmpty) height = double.parse(h);
29+
} catch (_) {}
30+
31+
final alt = attrs['alt'] ?? '';
32+
final lower = url.toLowerCase();
33+
final isNetwork = url.startsWith('http');
34+
final isSvg =
35+
lower.endsWith('.svg') ||
36+
attrs['type']?.toLowerCase() == 'image/svg+xml';
37+
38+
Widget buildError(Object error, {bool trySvg = false}) {
39+
if (errorBuilder != null) return errorBuilder(url, alt, error);
40+
if (trySvg) {
41+
return isNetwork
42+
? SvgPicture.network(
43+
url,
44+
width: width,
45+
height: height,
46+
fit: svgFit,
47+
alignment: alignment,
48+
clipBehavior: Clip.none,
49+
errorBuilder: (ctx, error, stack) => buildError(error),
50+
)
51+
: SvgPicture.asset(
52+
url,
53+
width: width,
54+
height: height,
55+
fit: svgFit,
56+
alignment: alignment,
57+
clipBehavior: Clip.none,
58+
errorBuilder: (ctx, error, stack) => buildError(error),
59+
);
60+
} else {
61+
return Row(
62+
mainAxisSize: MainAxisSize.min,
63+
children: [
64+
const Icon(
65+
Icons.broken_image,
66+
color: Colors.redAccent,
67+
size: 16,
68+
),
69+
if (alt.isNotEmpty) ...[
70+
const SizedBox(width: 6),
71+
Flexible(child: Text(alt)),
72+
],
73+
],
74+
);
75+
}
76+
}
77+
78+
Widget img;
79+
80+
if (isSvg) {
81+
img = isNetwork
82+
? SvgPicture.network(
83+
url,
84+
width: width,
85+
height: height,
86+
fit: svgFit,
87+
alignment: alignment,
88+
clipBehavior: Clip.none,
89+
errorBuilder: (ctx, error, stack) => buildError(error),
90+
)
91+
: SvgPicture.asset(
92+
url,
93+
width: width,
94+
height: height,
95+
fit: svgFit,
96+
alignment: alignment,
97+
clipBehavior: Clip.none,
98+
errorBuilder: (ctx, error, stack) => buildError(error),
99+
);
100+
} else {
101+
img = isNetwork
102+
? Image.network(
103+
url,
104+
width: width,
105+
height: height,
106+
fit: rasterFit,
107+
alignment: alignment,
108+
errorBuilder: (ctx, error, stack) =>
109+
buildError(error, trySvg: true),
110+
)
111+
: Image.asset(
112+
url,
113+
width: width,
114+
height: height,
115+
fit: rasterFit,
116+
alignment: alignment,
117+
errorBuilder: (ctx, error, stack) =>
118+
buildError(error, trySvg: true),
119+
);
120+
}
121+
122+
if (!wrapWithViewer) return img;
123+
124+
return Builder(
125+
builder: (context) {
126+
return InkWell(
127+
child: Hero(tag: img.hashCode, child: img),
128+
onTap: () async {
129+
await Navigator.of(context).push(
130+
PageRouteBuilder(
131+
opaque: false,
132+
pageBuilder: (_, _, _) => _ImageViewer(child: img),
133+
),
134+
);
135+
},
136+
);
137+
},
138+
);
139+
},
140+
);
141+
}
142+
143+
class _ImageViewer extends StatelessWidget {
144+
final Widget child;
145+
146+
const _ImageViewer({required this.child});
147+
148+
@override
149+
Widget build(BuildContext context) {
150+
return GestureDetector(
151+
onTap: () => Navigator.of(context).pop(),
152+
child: Scaffold(
153+
backgroundColor: Colors.black.toOpacity(0.3),
154+
body: Stack(
155+
fit: StackFit.expand,
156+
children: [
157+
InteractiveViewer(
158+
child: Center(
159+
child: Hero(tag: child.hashCode, child: child),
160+
),
161+
),
162+
Align(
163+
alignment: Alignment.bottomCenter,
164+
child: Padding(
165+
padding: const EdgeInsets.only(bottom: 24),
166+
child: IconButton(
167+
onPressed: () => Navigator.of(context).pop(),
168+
icon: Container(
169+
width: 40,
170+
height: 40,
171+
decoration: BoxDecoration(
172+
color: Colors.white.toOpacity(0.2),
173+
shape: BoxShape.circle,
174+
),
175+
child: const Icon(Icons.clear, color: Colors.grey),
176+
),
177+
),
178+
),
179+
),
180+
],
181+
),
182+
),
183+
);
184+
}
185+
}

pubspec.lock

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ packages:
139139
url: "https://pub.dev"
140140
source: hosted
141141
version: "2.0.30"
142+
flutter_svg:
143+
dependency: "direct main"
144+
description:
145+
name: flutter_svg
146+
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
147+
url: "https://pub.dev"
148+
source: hosted
149+
version: "2.2.1"
142150
flutter_test:
143151
dependency: "direct dev"
144152
description: flutter
@@ -157,6 +165,22 @@ packages:
157165
url: "https://pub.dev"
158166
source: hosted
159167
version: "0.7.0"
168+
http:
169+
dependency: transitive
170+
description:
171+
name: http
172+
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
173+
url: "https://pub.dev"
174+
source: hosted
175+
version: "1.5.0"
176+
http_parser:
177+
dependency: transitive
178+
description:
179+
name: http_parser
180+
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
181+
url: "https://pub.dev"
182+
source: hosted
183+
version: "4.1.2"
160184
intl:
161185
dependency: "direct main"
162186
description:
@@ -245,6 +269,14 @@ packages:
245269
url: "https://pub.dev"
246270
source: hosted
247271
version: "1.9.1"
272+
path_parsing:
273+
dependency: transitive
274+
description:
275+
name: path_parsing
276+
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
277+
url: "https://pub.dev"
278+
source: hosted
279+
version: "1.1.0"
248280
path_provider_linux:
249281
dependency: transitive
250282
description:
@@ -458,6 +490,14 @@ packages:
458490
url: "https://pub.dev"
459491
source: hosted
460492
version: "0.7.6"
493+
typed_data:
494+
dependency: transitive
495+
description:
496+
name: typed_data
497+
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
498+
url: "https://pub.dev"
499+
source: hosted
500+
version: "1.4.0"
461501
url_launcher:
462502
dependency: "direct main"
463503
description:
@@ -522,6 +562,30 @@ packages:
522562
url: "https://pub.dev"
523563
source: hosted
524564
version: "3.1.4"
565+
vector_graphics:
566+
dependency: transitive
567+
description:
568+
name: vector_graphics
569+
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
570+
url: "https://pub.dev"
571+
source: hosted
572+
version: "1.1.19"
573+
vector_graphics_codec:
574+
dependency: transitive
575+
description:
576+
name: vector_graphics_codec
577+
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
578+
url: "https://pub.dev"
579+
source: hosted
580+
version: "1.1.13"
581+
vector_graphics_compiler:
582+
dependency: transitive
583+
description:
584+
name: vector_graphics_compiler
585+
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
586+
url: "https://pub.dev"
587+
source: hosted
588+
version: "1.1.19"
525589
vector_math:
526590
dependency: transitive
527591
description:

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies:
1616
sdk: flutter
1717
flutter_localizations:
1818
sdk: flutter
19+
flutter_svg: ^2.2.1
1920
intl: any
2021
markdown_widget: ^2.3.2+8
2122
permission_handler: ^12.0.1

0 commit comments

Comments
 (0)