flutter_richify 0.0.1 copy "flutter_richify: ^0.0.1" to clipboard
flutter_richify: ^0.0.1 copied to clipboard

A Flutter package to make it easy to create links, mentions, hashtags, emails, phones, and custom patterns.

example/lib/main.dart

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

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Richify Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const RichifyDemo(),
    );
  }
}

class RichifyDemo extends StatefulWidget {
  const RichifyDemo({super.key});

  @override
  State<RichifyDemo> createState() => _RichifyDemoState();
}

class _RichifyDemoState extends State<RichifyDemo> {
  late RichifyController _socialController;
  late RichifyController _emailController;
  late RichifyController _linkController;

  final List<String> _detectedMentions = [];
  final List<String> _detectedHashtags = [];
  final List<String> _detectedEmails = [];
  String _lastClickedItem = '';

  @override
  void initState() {
    super.initState();

    // Social media style input with mentions and hashtags
    _socialController = RichifyController(
      text: 'Hey @john! Check out #flutter_richify for rich text patterns',
      matchers: [
        // Mentions matcher
        RegexMatcher(
          pattern: RegExp(r'@\w+'),
          spanBuilder: (candidate) => TextSpan(
            text: candidate.text,
            style: const TextStyle(
              color: Colors.blue,
              fontWeight: FontWeight.bold,
            ),
          ),
          options: const TextMatcherOptions(
            deleteOnBack: true, // Delete entire mention on backspace
          ),
        ),
        // Hashtags matcher
        RegexMatcher(
          pattern: RegExp(r'#\w+'),
          spanBuilder: (candidate) => TextSpan(
            text: candidate.text,
            style: const TextStyle(
              color: Colors.green,
              fontWeight: FontWeight.w600,
            ),
          ),
          options: const TextMatcherOptions(
            deleteOnBack: true,
          ),
        ),
      ],
      onMatch: (matches) {
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted) {
            setState(() {
              _detectedMentions.clear();
              _detectedHashtags.clear();
              for (final match in matches) {
                if (match.startsWith('@')) {
                  _detectedMentions.add(match);
                } else if (match.startsWith('#')) {
                  _detectedHashtags.add(match);
                }
              }
            });
          }
        });
      },
    );

    // Email input with chip-like behavior
    _emailController = RichifyController(
      text: '',
      blockCursorMovement: true, // Gmail-like behavior
      matchers: [
        RegexMatcher(
          pattern: RegExp(r'\S+@\S+\.\S+'),
          spanBuilder: (candidate) => WidgetSpan(
            alignment: PlaceholderAlignment.middle,
            child: Container(
              margin: const EdgeInsets.symmetric(horizontal: 2, vertical: 2),
              padding: const EdgeInsets.fromLTRB(10, 4, 4, 4),
              decoration: BoxDecoration(
                color: Colors.blue.shade100,
                borderRadius: BorderRadius.circular(16),
                border: Border.all(color: Colors.blue.shade300),
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                    candidate.text,
                    style: const TextStyle(
                      color: Colors.blue,
                      fontSize: 14,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const SizedBox(width: 4),
                  GestureDetector(
                    onTap: () {
                      // Remove the email by replacing it with empty string
                      final currentText = _emailController.text;
                      final newText = currentText
                          .replaceFirst(candidate.text, '')
                          .trim();
                      _emailController.text = newText;
                      _emailController.selection = TextSelection.collapsed(
                        offset: newText.length,
                      );
                    },
                    child: MouseRegion(
                      cursor: SystemMouseCursors.click,
                      child: Icon(
                        Icons.close,
                        size: 16,
                        color: Colors.blue.shade700,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
          options: const TextMatcherOptions(
            deleteOnBack: true,
          ),
        ),
      ],
      onMatch: (matches) {
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted) {
            setState(() {
              _detectedEmails.clear();
              _detectedEmails.addAll(matches);
            });
          }
        });
      },
    );

    // URL and phone number detector with interactions
    _linkController = RichifyController(
      text: 'Visit https://flutter.dev or call +1-234-567-8900',
      matchers: [
        // URL matcher with tap interaction
        RegexMatcher(
          pattern: RegExp(
            r'https?://[^\s]+',
            caseSensitive: false,
          ),
          spanBuilder: (candidate) => WidgetSpan(
            alignment: PlaceholderAlignment.baseline,
            baseline: TextBaseline.alphabetic,
            child: GestureDetector(
              onTap: () {
                setState(() {
                  _lastClickedItem = 'URL: ${candidate.text}';
                });
                // In a real app, you would launch the URL here
                // e.g., launchUrl(Uri.parse(candidate.text));
              },
              child: MouseRegion(
                cursor: SystemMouseCursors.click,
                child: Text(
                  candidate.text,
                  style: const TextStyle(
                    color: Colors.blue,
                    decoration: TextDecoration.underline,
                  ),
                ),
              ),
            ),
          ),
          priority: 2, // Higher priority than phone numbers
        ),
        // Phone number matcher with tap interaction
        RegexMatcher(
          pattern: RegExp(
            r'\+?\d{1,3}[-.\s]?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}',
          ),
          spanBuilder: (candidate) => WidgetSpan(
            alignment: PlaceholderAlignment.baseline,
            baseline: TextBaseline.alphabetic,
            child: GestureDetector(
              onTap: () {
                setState(() {
                  _lastClickedItem = 'Phone: ${candidate.text}';
                });
                // In a real app, you would launch the phone dialer here
                // e.g., launchUrl(Uri.parse('tel:${candidate.text}'));
              },
              child: MouseRegion(
                cursor: SystemMouseCursors.click,
                child: Text(
                  candidate.text,
                  style: const TextStyle(
                    color: Colors.orange,
                    fontWeight: FontWeight.w500,
                    decoration: TextDecoration.underline,
                    decorationStyle: TextDecorationStyle.dotted,
                  ),
                ),
              ),
            ),
          ),
          priority: 1,
        ),
      ],
    );
  }

  @override
  void dispose() {
    _socialController.dispose();
    _emailController.dispose();
    _linkController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Richify Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Social media example
            _buildSection(
              title: 'Mentions & Hashtags',
              subtitle: 'Social media style input',
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextField(
                    controller: _socialController,
                    decoration: const InputDecoration(
                      hintText: 'Type @username or #hashtag...',
                      border: OutlineInputBorder(),
                    ),
                    maxLines: 3,
                  ),
                  if (_detectedMentions.isNotEmpty ||
                      _detectedHashtags.isNotEmpty)
                    Padding(
                      padding: const EdgeInsets.only(top: 8),
                      child: Wrap(
                        spacing: 8,
                        runSpacing: 4,
                        children: [
                          if (_detectedMentions.isNotEmpty)
                            _buildChip(
                              'Mentions: ${_detectedMentions.join(', ')}',
                              Colors.blue,
                            ),
                          if (_detectedHashtags.isNotEmpty)
                            _buildChip(
                              'Hashtags: ${_detectedHashtags.join(', ')}',
                              Colors.green,
                            ),
                        ],
                      ),
                    ),
                ],
              ),
            ),
            const SizedBox(height: 24),

            // Email input example
            _buildSection(
              title: 'Email Chips',
              subtitle: 'Gmail-style recipient input',
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextField(
                    controller: _emailController,
                    decoration: const InputDecoration(
                      hintText: 'Add recipients (e.g., user@example.com)...',
                      border: OutlineInputBorder(),
                    ),
                    maxLines: 2,
                  ),
                  if (_detectedEmails.isNotEmpty)
                    Padding(
                      padding: const EdgeInsets.only(top: 8),
                      child: _buildChip(
                        'Recipients: ${_detectedEmails.length}',
                        Colors.blue,
                      ),
                    ),
                ],
              ),
            ),
            const SizedBox(height: 24),

            // URL and phone example
            _buildSection(
              title: 'Links & Phone Numbers',
              subtitle: 'Click to interact',
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  TextField(
                    controller: _linkController,
                    decoration: const InputDecoration(
                      hintText: 'Paste URLs or phone numbers...',
                      border: OutlineInputBorder(),
                    ),
                    maxLines: 3,
                  ),
                  if (_lastClickedItem.isNotEmpty)
                    Padding(
                      padding: const EdgeInsets.only(top: 8),
                      child: Container(
                        padding: const EdgeInsets.all(8),
                        decoration: BoxDecoration(
                          color: Colors.green.shade50,
                          borderRadius: BorderRadius.circular(4),
                          border: Border.all(color: Colors.green.shade200),
                        ),
                        child: Row(
                          children: [
                            Icon(
                              Icons.touch_app,
                              size: 16,
                              color: Colors.green.shade700,
                            ),
                            const SizedBox(width: 8),
                            Expanded(
                              child: Text(
                                'Clicked: $_lastClickedItem',
                                style: TextStyle(
                                  color: Colors.green.shade900,
                                  fontSize: 13,
                                ),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSection({
    required String title,
    required String subtitle,
    required Widget child,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 4),
        Text(
          subtitle,
          style: TextStyle(
            fontSize: 14,
            color: Colors.grey.shade600,
          ),
        ),
        const SizedBox(height: 12),
        child,
      ],
    );
  }

  Widget _buildChip(String label, Color color) {
    return Chip(
      label: Text(label),
      backgroundColor: color.withValues(alpha: 0.1),
      labelStyle: TextStyle(
        color: color,
        fontSize: 12,
        fontWeight: FontWeight.w500,
      ),
      visualDensity: VisualDensity.compact,
    );
  }
}
0
likes
150
points
61
downloads

Publisher

verified publisherdegenk.com

Weekly Downloads

A Flutter package to make it easy to create links, mentions, hashtags, emails, phones, and custom patterns.

Repository (GitHub)
View/report issues

Topics

#rich-text #textfield

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_richify