flutter_sanity_portable_text 1.4.1 copy "flutter_sanity_portable_text: ^1.4.1" to clipboard
flutter_sanity_portable_text: ^1.4.1 copied to clipboard

Flutter renderer for the Sanity.io Portable Text format with support for standard and custom blocks, spans, annotations, and styles.

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_sanity_portable_text/flutter_sanity_portable_text.dart';

final class UnregisteredBlockItem implements PortableBlockItem {
  @override
  String get blockType => 'unregistered';
}

final class CustomBlockItem implements PortableBlockItem {
  CustomBlockItem({
    required this.text,
    required this.foregroundColor,
    required this.backgroundColor,
  });

  @override
  String get blockType => 'custom';

  final String text;
  final Color foregroundColor;
  final Color backgroundColor;
}

final class CustomMarkDef implements MarkDef {
  CustomMarkDef({
    required this.color,
    required this.key,
  });

  @override
  final String key;

  final Color color;

  @override
  String get type => 'custom-mark';
}

final class UnregisteredMarkDef implements MarkDef {
  UnregisteredMarkDef({
    required this.key,
  });

  @override
  final String key;

  @override
  String get type => 'unregistered-mark';
}

void main() {
  // Registering a custom block
  _registerCustomBlock();

  // Registering a custom mark
  _registerCustomMark();

  // Registering a custom block style
  _registerCustomBlockStyle();

  runApp(const MyApp());
}

void _registerCustomBlockStyle() {
  PortableTextConfig.shared.styles['custom-style'] =
      (final BuildContext context, final TextStyle base) {
    final theme = Theme.of(context);

    return base.copyWith(
      color: theme.colorScheme.primary,
      fontSize: 18,
      fontWeight: FontWeight.bold,
    );
  };
}

void _registerCustomMark() {
  PortableTextConfig.shared.markDefs['custom-mark'] = MarkDefDescriptor(
    schemaType: 'custom-mark',
    styleBuilder: (context, markDef, textStyle) {
      final mark = markDef as CustomMarkDef;

      final style = textStyle.apply(
        decoration: TextDecoration.underline,
        decorationColor: mark.color,
      );

      return style;
    },
    spanBuilder: (context, markDef, text, style) {
      return WidgetSpan(
        child: Container(
          color: Colors.black12,
          padding: const EdgeInsets.only(right: 4),
          child: GestureDetector(
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(
                  Icons.bolt,
                  color: (markDef as CustomMarkDef).color,
                  size: 20,
                ),
                Text(text, style: style),
              ],
            ),
            onTap: () => debugPrint('Tapped on $markDef'),
          ),
        ),
      );
    },
    fromJson: (json) => CustomMarkDef(color: json['color'], key: json['key']),
  );
}

void _registerCustomBlock() {
  PortableTextConfig.shared.blocks['custom'] = (context, item) {
    final theme = Theme.of(context);
    final custom = item as CustomBlockItem;
    final style =
        theme.textTheme.bodyMedium?.apply(color: custom.foregroundColor);

    return Container(
      decoration: BoxDecoration(
          color: custom.backgroundColor,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: Colors.blueAccent, width: 2),
          boxShadow: const [
            BoxShadow(
              offset: Offset(0, 4),
              blurRadius: 2,
              color: Colors.black26,
            )
          ]),
      margin: const EdgeInsets.only(bottom: 8),
      padding: const EdgeInsets.all(8),
      child: Text(custom.text, style: style),
    );
  };
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: Scaffold(
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: PortableText(
              blocks: [
                TextBlockItem(
                  children: [
                    Span(
                      text: 'Sanity Portable Text',
                    ),
                  ],
                  style: 'h1',
                ),

                // Directly from JSON
                TextBlockItem.fromJson(jsonDecode('''
    {
      "_type": "block",
      "style": "h3",
      "children": [
        {
          "_type": "span",
          "text": "Rendered in "
        },
        {
          "_type": "span",
          "text": "Flutter",
          "marks": ["em", "strong", "underline"]
        }
      ]
    }
  ''')),

                // Let's try a blockquote now
                TextBlockItem(
                  children: [
                    Span(
                      text:
                          '"The best way to predict the future is to invent it."',
                    ),
                    Span(
                      text: '\n- Steve Jobs',
                    ),
                  ],
                  style: 'blockquote',
                ),

                TextBlockItem(
                  children: [
                    Span(
                      text:
                          'Supports all standard marks and styles, including support for:',
                    ),
                  ],
                ),

                _listItem('Bulleted text', ListItemType.bullet),
                _listItem('Numbered text', ListItemType.number),
                _listItem('Square bullet text', ListItemType.square),
                TextBlockItem(
                  children: [
                    Span(text: 'Strong text, ', marks: ['strong']),
                    Span(text: 'Emphasized text, ', marks: ['em']),
                    Span(text: 'Underlined text, ', marks: ['underline']),
                    Span(
                        text: 'Strike through text, ',
                        marks: ['strike-through']),
                    Span(
                        text: 'All combined',
                        marks: ['strong', 'em', 'underline', 'strike-through']),
                    Span(text: '.'),
                  ],
                ),
                _textBlock('Custom Styles', style: 'custom-style'),
                _textBlock('Report errors for missing styles...'),
                _textBlock('Unregistered Styles', style: 'unregistered-style'),
                _textBlock('And not to forget...the unsung'),
                for (final index in [1, 2, 3, 4, 5, 6])
                  _textBlock('H$index', style: 'h$index'),
                CustomBlockItem(
                    text: 'We can also do custom blocks!',
                    foregroundColor: Colors.white,
                    backgroundColor: Colors.primaries[4]),
                _textBlock(
                    ('And report when a block item is not registered for rendering..')),
                UnregisteredBlockItem(),
                TextBlockItem(
                  children: [
                    Span(
                      text: 'We can also do ',
                    ),
                    Span(text: 'custom marks!', marks: ['custom-key']),
                    Span(
                        text:
                            ' and report when a custom mark is not registered, such as:'),
                    Span(text: ' this.', marks: ['missing-key']),
                  ],
                  markDefs: [
                    CustomMarkDef(color: Colors.red, key: 'custom-key'),
                    UnregisteredMarkDef(key: 'missing-key')
                  ],
                ),
                TextBlockItem(children: [
                  Span(text: '\nAnd there ends the quick tour of '),
                  Span(text: 'Sanity Portable Text. ', marks: ['strong', 'em']),
                  Span(text: 'Now go make some noise in portable text!'),
                ])
              ],
            ),
          ),
        ),
      ),
    );
  }
}

TextBlockItem _textBlock(String text, {String? style}) {
  return TextBlockItem(
    children: [
      Span(text: text),
    ],
    style: style ?? 'normal',
  );
}

TextBlockItem _listItem(String text, ListItemType type) {
  return TextBlockItem(
    children: [
      Span(text: text),
    ],
    listItem: type,
  );
}
7
likes
0
points
2.42k
downloads

Publisher

verified publishervyuh.tech

Weekly Downloads

Flutter renderer for the Sanity.io Portable Text format with support for standard and custom blocks, spans, annotations, and styles.

Homepage
Repository (GitHub)
View/report issues

Topics

#flutter #portable-text #renderer #json

License

unknown (license)

Dependencies

collection, flutter, json_annotation, nanoid

More

Packages that depend on flutter_sanity_portable_text