Skip to content

[flutter_markdown] Ensure customize nested bullet list style. #6384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 19, 2024
6 changes: 6 additions & 0 deletions packages/flutter_markdown/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.7.0

* **BREAKING CHANGES**:
* Replaces parameters at `bulletBuilder` with `MarkdownBulletParameters`.
* Introduces a new parameter `nestLevel` that exposes the bullet item's nesting level.

## 0.6.23

* Gracefully handle image dimension parsing failures.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// TODO(goderbauer): Restructure the examples to avoid this ignore, https://github.com/flutter/flutter/issues/110208.
// ignore_for_file: avoid_implementing_value_types

import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import '../shared/markdown_demo_widget.dart';

// ignore_for_file: public_member_api_docs

const String _markdownData = '''
# Custom Ordered List Demo

## Unordered List

- first
- second
- first
- first
- second
- first
- second

## Ordered List

1. first
2. second
1. first
1. first
2. second
1. first
1. second
''';

const String _notes = '''
# Custom Bullet List Demo
---

## Overview

This is the custom bullet list demo. This demo shows how to customize the bullet list style.
This demo example is being preserved for reference purposes.
''';

class CustomBulletListDemo extends StatelessWidget
implements MarkdownDemoWidget {
const CustomBulletListDemo({super.key});

static const String _title = 'Custom Bullet List Demo';

@override
String get title => CustomBulletListDemo._title;

@override
String get description => 'Shows how to customize the bullet list style.';

@override
Future<String> get data => Future<String>.value(_markdownData);

@override
Future<String> get notes => Future<String>.value(_notes);

@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Markdown(
data: _markdownData,
bulletBuilder: (MarkdownBulletParameters parameters) => FittedBox(
fit: BoxFit.scaleDown,
child: switch (parameters.style) {
BulletStyle.unorderedList => const Text('・'),
BulletStyle.orderedList =>
Text('${parameters.nestLevel}-${parameters.index + 1}.'),
},
),
),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'package:flutter/material.dart';
import '../demos/basic_markdown_demo.dart';
import '../demos/centered_header_demo.dart';
import '../demos/custom_bullet_list_demo.dart';
import '../demos/extended_emoji_demo.dart';
import '../demos/markdown_body_shrink_wrap_demo.dart';
import '../demos/minimal_markdown_demo.dart';
Expand All @@ -30,6 +31,7 @@ class HomeScreen extends StatelessWidget {
OriginalMarkdownDemo(),
const CenteredHeaderDemo(),
const MarkdownBodyShrinkWrapDemo(),
const CustomBulletListDemo(),
];

@override
Expand Down
11 changes: 9 additions & 2 deletions packages/flutter_markdown/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,15 @@ class MarkdownBuilder implements md.NodeVisitor {
if (bulletBuilder != null) {
return Padding(
padding: styleSheet.listBulletPadding!,
child: bulletBuilder!(index,
isUnordered ? BulletStyle.unorderedList : BulletStyle.orderedList),
child: bulletBuilder!(
MarkdownBulletParameters(
index: index,
style: isUnordered
? BulletStyle.unorderedList
: BulletStyle.orderedList,
nestLevel: _listIndents.length - 1,
),
),
);
}

Expand Down
25 changes: 24 additions & 1 deletion packages/flutter_markdown/lib/src/widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,30 @@ typedef MarkdownCheckboxBuilder = Widget Function(bool value);
/// Signature for custom bullet widget.
///
/// Used by [MarkdownWidget.bulletBuilder]
typedef MarkdownBulletBuilder = Widget Function(int index, BulletStyle style);
typedef MarkdownBulletBuilder = Widget Function(
MarkdownBulletParameters parameters,
);

/// An parameters of [MarkdownBulletBuilder].
///
/// Used by [MarkdownWidget.bulletBuilder]
class MarkdownBulletParameters {
/// Creates a new instance of [MarkdownBulletParameters].
const MarkdownBulletParameters({
required this.index,
required this.style,
required this.nestLevel,
});

/// The index of the bullet on that nesting level.
final int index;

/// The style of the bullet.
final BulletStyle style;

/// The nest level of the bullet.
final int nestLevel;
}

/// Enumeration sent to the user when calling [MarkdownBulletBuilder]
///
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_markdown/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Markdown renderer for Flutter. Create rich text output,
formatted with simple Markdown tags.
repository: https://github.com/flutter/packages/tree/main/packages/flutter_markdown
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_markdown%22
version: 0.6.23
version: 0.7.0

environment:
sdk: ^3.3.0
Expand Down
62 changes: 60 additions & 2 deletions packages/flutter_markdown/test/list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,35 @@ void defineTests() {
]);
},
);

testWidgets('custom bullet builder', (WidgetTester tester) async {
const String data =
'* Item 1\n * Item 2\n * Item 3\n * Item 4\n* Item 5';
Widget builder(MarkdownBulletParameters parameters) => Text(
'${parameters.index} ${parameters.style == BulletStyle.orderedList ? 'ordered' : 'unordered'} ${parameters.nestLevel}',
);

await tester.pumpWidget(
boilerplate(
Markdown(data: data, bulletBuilder: builder),
),
);

final Iterable<Widget> widgets = tester.allWidgets;

expectTextStrings(widgets, <String>[
'0 unordered 0',
'Item 1',
'0 unordered 1',
'Item 2',
'0 unordered 2',
'Item 3',
'1 unordered 1',
'Item 4',
'1 unordered 0',
'Item 5',
]);
});
});

group('Ordered List', () {
Expand Down Expand Up @@ -135,6 +164,35 @@ void defineTests() {
final Iterable<Widget> widgets = tester.allWidgets;
expectTextStrings(widgets, <String>['1.', 'one', 'two']);
});

testWidgets('custom bullet builder', (WidgetTester tester) async {
const String data =
'1. Item 1\n 1. Item 2\n 1. Item 3\n 1. Item 4\n1. Item 5';
Widget builder(MarkdownBulletParameters parameters) => Text(
'${parameters.index} ${parameters.style == BulletStyle.orderedList ? 'ordered' : 'unordered'} ${parameters.nestLevel}',
);

await tester.pumpWidget(
boilerplate(
Markdown(data: data, bulletBuilder: builder),
),
);

final Iterable<Widget> widgets = tester.allWidgets;

expectTextStrings(widgets, <String>[
'0 ordered 0',
'Item 1',
'0 ordered 1',
'Item 2',
'0 ordered 2',
'Item 3',
'1 ordered 1',
'Item 4',
'1 ordered 0',
'Item 5',
]);
});
});

group('Task List', () {
Expand All @@ -161,8 +219,8 @@ void defineTests() {

testWidgets('custom bullet builder', (WidgetTester tester) async {
const String data = '* Item 1\n* Item 2\n1) Item 3\n2) Item 4';
Widget builder(int index, BulletStyle style) => Text(
'$index ${style == BulletStyle.orderedList ? 'ordered' : 'unordered'}');
Widget builder(MarkdownBulletParameters parameters) => Text(
'${parameters.index} ${parameters.style == BulletStyle.orderedList ? 'ordered' : 'unordered'}');

await tester.pumpWidget(
boilerplate(
Expand Down