flutter_sanity_portable_text 1.0.0-beta.22 copy "flutter_sanity_portable_text: ^1.0.0-beta.22" to clipboard
flutter_sanity_portable_text: ^1.0.0-beta.22 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,
  );
}
2
likes
140
pub points
68%
popularity
screenshot

Publisher

verified publishervyuh.tech

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
Contributing

Topics

#flutter #portable-text #renderer #json

Documentation

API reference

License

MIT (LICENSE)

Dependencies

collection, flutter, json_annotation, nanoid

More

Packages that depend on flutter_sanity_portable_text