Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ MarkdownTheme(
)
```

Or you can use the `MarkdownThemeData.mergeTheme(Theme.of(context))` factory to create a theme that inherits from the application's theme.
This approach allows you to easily support both light and dark themes, and keeps your markdown styling consistent with the rest of your application.

### Custom Block Painters

For advanced customization, you can provide custom block painters:
Expand Down
69 changes: 55 additions & 14 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_md/flutter_md.dart';

void main() => runZonedGuarded<void>(
() => runApp(const App()),
() => runApp(ThemeModel(
notifier: ValueNotifier<ThemeMode>(ThemeMode.dark),
child: const App())),
(e, s) => print(e),
);

Expand All @@ -17,21 +19,16 @@ class App extends StatelessWidget {
/// {@macro app}
const App({super.key});

/// Light theme for the app.
static final ThemeData theme = ThemeData.light();

@override
Widget build(BuildContext context) => MaterialApp(
title: 'Markdown',
themeMode: ThemeMode.light,
theme: theme,
darkTheme: theme,
themeMode: ThemeModel.of(context).value,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const HomeScreen(),
builder: (context, child) => MarkdownTheme(
data: MarkdownThemeData(
textStyle: const TextStyle(fontSize: 14.0, color: Colors.black),
textDirection: TextDirection.ltr,
textScaler: TextScaler.noScaling,
data: MarkdownThemeData.mergeTheme(
Theme.of(context),
// Exclude images from the markdown rendering,
// so they are not rendered in the output.
// Because image spans are not supported yet.
Expand All @@ -52,6 +49,42 @@ class App extends StatelessWidget {
);
}

/// {@template theme_model}
/// A widget that provides the [ThemeMode] to its descendants.
/// {@endtemplate}
class ThemeModel extends InheritedNotifier<ValueNotifier<ThemeMode>> {
/// {@macro theme_model}
const ThemeModel({super.key, super.notifier, required super.child});

/// The state from the closest instance of this class
/// that encloses the given context, if any.
/// e.g. `Theme.maybeOf(context)`.
static ValueNotifier<ThemeMode>? maybeOf(BuildContext context,
{bool listen = true}) =>
listen
? context.dependOnInheritedWidgetOfExactType<ThemeModel>()?.notifier
: context.getInheritedWidgetOfExactType<ThemeModel>()?.notifier;

static Never _notFoundInheritedWidgetOfExactType() => throw ArgumentError(
'Out of scope, not found inherited widget '
'a ThemeModel of the exact type',
'out_of_scope',
);

/// The state from the closest instance of this class
/// that encloses the given context.
/// e.g. `Theme.of(context)`
static ValueNotifier<ThemeMode> of(BuildContext context,
{bool listen = true}) =>
maybeOf(context, listen: listen) ?? _notFoundInheritedWidgetOfExactType();

@override
bool updateShouldNotify(
covariant InheritedNotifier<ValueNotifier<ThemeMode>> oldWidget) {
return !identical(notifier, oldWidget.notifier);
}
}

/// {@template home_screen}
/// HomeScreen widget.
/// {@endtemplate}
Expand Down Expand Up @@ -104,9 +137,17 @@ class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Markdown'),
),
centerTitle: true,
title: const Text('Markdown'),
actions: <Widget>[
// theme switch widget
Switch.adaptive(
value: ThemeModel.of(context).value == ThemeMode.dark,
onChanged: (value) {
ThemeModel.of(context).value =
value ? ThemeMode.dark : ThemeMode.light;
}),
]),
body: SafeArea(
child: CustomMultiChildLayout(
delegate: _layoutDelegate,
Expand Down
112 changes: 30 additions & 82 deletions lib/src/render.dart
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ class BlockPainter$Quote with ParagraphGestureHandler implements BlockPainter {
required List<MD$Span> spans,
required this.indent,
required this.theme,
}) : painter = TextPainter(
}) : painter = TextPainter(
text: _paragraphFromMarkdownSpans(
spans: spans,
theme: theme,
Expand All @@ -740,7 +740,13 @@ class BlockPainter$Quote with ParagraphGestureHandler implements BlockPainter {
textAlign: TextAlign.start,
textDirection: theme.textDirection,
textScaler: theme.textScaler,
);
),
linePaint = Paint()
..color = theme.dividerColor ??
const Color(0x7F7F7F7F) // Gray color for the line.
..isAntiAlias = false
..strokeWidth = 4.0
..style = PaintingStyle.fill;

final MarkdownThemeData theme;

Expand All @@ -750,11 +756,7 @@ class BlockPainter$Quote with ParagraphGestureHandler implements BlockPainter {

static const double lineIndent = 10.0; // Indentation for quote blocks.

static final Paint linePaint = Paint()
..color = const Color(0x7F7F7F7F) // Gray color for the line.
..isAntiAlias = false
..strokeWidth = 4.0
..style = PaintingStyle.fill;
final Paint linePaint;

@override
Size get size => _size;
Expand Down Expand Up @@ -801,74 +803,19 @@ class BlockPainter$Quote with ParagraphGestureHandler implements BlockPainter {
// If the width is less than required do not paint anything.
if (size.width < _size.width) return;

canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, offset, size.width, _size.height),
const Radius.circular(4.0), // Rounded corners for the quote block.
),
Paint()
..color = const Color.fromARGB(255, 235, 235, 235)
..isAntiAlias = false
..style = PaintingStyle.fill,
);

{
// --- Icons.format_quote_outlined --- //
try {
const quoteCodePoint = 0xf0a9;
const quoteFamily = 'MaterialIcons';
final textStyle = TextStyle(
fontFamily: quoteFamily,
fontSize:
theme.quoteStyle?.fontSize ?? theme.textStyle.fontSize ?? 14.0,
color: const Color(0xFF7F7F7F), // Gray color for the quote icon.
);
final quotePainter = TextPainter(
text: TextSpan(
text: String.fromCharCode(quoteCodePoint),
style: textStyle,
),
textAlign: TextAlign.start,
textDirection: theme.textDirection,
textScaler: theme.textScaler,
)..layout();
canvas
..save()
..translate(
_size.width + quotePainter.width,
offset + _size.height,
)
..rotate(math.pi);
quotePainter.paint(
canvas,
Offset(
_size.width,
_size.height - quotePainter.height,
),
);
canvas.restore();
quotePainter.paint(
canvas,
Offset(
size.width - quotePainter.width - 2.0,
offset + _size.height - quotePainter.height,
),
);
} on Object {
for (var i = 1; i <= indent; i++)
canvas.drawLine(
Offset(
i * lineIndent - lineIndent / 2,
offset + 12,
),
Offset(
i * lineIndent - lineIndent / 2,
offset + _size.height - 12,
),
linePaint,
);
}
}
// --- Draw vertical lines --- //
for (var i = 1; i <= indent; i++)
canvas.drawLine(
Offset(
i * lineIndent,
offset,
),
Offset(
i * lineIndent,
offset + _size.height,
),
linePaint,
);

painter.paint(
canvas,
Expand Down Expand Up @@ -1025,7 +972,7 @@ class BlockPainter$Spacer implements BlockPainter {

@override
Size layout(double width) {
final height = theme.textStyle.fontSize ?? 14.0;
final height = theme.textStyle.fontSize ?? kDefaultFontSize;
return _size = Size(0, height * count);
}

Expand Down Expand Up @@ -1065,7 +1012,7 @@ class BlockPainter$Divider implements BlockPainter {

@override
Size layout(double width) {
final height = theme.textStyle.fontSize ?? 14.0;
final height = theme.textStyle.fontSize ?? kDefaultFontSize;
return _size = Size(0, height);
}

Expand Down Expand Up @@ -1093,7 +1040,7 @@ class BlockPainter$Code implements BlockPainter {
text: text,
style: theme.textStyle.copyWith(
fontFamily: 'monospace',
fontSize: theme.textStyle.fontSize ?? 14.0,
fontSize: theme.textStyle.fontSize ?? kDefaultFontSize,
),
),
textAlign: TextAlign.start,
Expand Down Expand Up @@ -1144,7 +1091,7 @@ class BlockPainter$Code implements BlockPainter {
const Radius.circular(padding),
),
Paint()
..color = const Color.fromARGB(255, 235, 235, 235)
..color = theme.surfaceColor ?? const Color.fromARGB(255, 235, 235, 235)
..isAntiAlias = false
..style = PaintingStyle.fill,
);
Expand Down Expand Up @@ -1203,7 +1150,7 @@ class BlockPainter$Table implements BlockPainter {
return _size = Size(
width, // The width of the table is the same as the available width.
(header.cells.length + rows.length) *
((theme.textStyle.fontSize ?? 14.0) + padding * 2),
((theme.textStyle.fontSize ?? kDefaultFontSize) + padding * 2),
);
}

Expand All @@ -1215,7 +1162,8 @@ class BlockPainter$Table implements BlockPainter {
// Draw the header row.
final columnWidth = size.width / columns;
final cellMaxWidth = columnWidth - padding * 2;
final rowHeight = (theme.textStyle.fontSize ?? 14.0) + padding * 2;
final rowHeight =
(theme.textStyle.fontSize ?? kDefaultFontSize) + padding * 2;
canvas.drawRRect(
RRect.fromLTRBR(
0, // Left
Expand All @@ -1225,7 +1173,7 @@ class BlockPainter$Table implements BlockPainter {
const Radius.circular(padding), // Radius for rounded corners
),
Paint()
..color = const Color.fromARGB(255, 235, 235, 235)
..color = theme.surfaceColor ?? const Color.fromARGB(255, 235, 235, 235)
..style = PaintingStyle.fill
..isAntiAlias = false,
);
Expand Down
Loading