multiline_prefix_text_field 0.0.4 copy "multiline_prefix_text_field: ^0.0.4" to clipboard
multiline_prefix_text_field: ^0.0.4 copied to clipboard

A Flutter widget that provides a multiline text field with automatic numbered prefixes (1., 2., 3.), ideal for lists, todos, and structured text input.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:multiline_prefix_text_field/multiline_prefix_text_field.dart';
import 'widgets/unfocus_on_tap_outside_field_handler.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Multiline Prefix TextField Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6366F1),
          brightness: Brightness.light,
        ),
        textTheme: GoogleFonts.plusJakartaSansTextTheme(),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF818CF8),
          brightness: Brightness.dark,
        ),
        textTheme: GoogleFonts.plusJakartaSansTextTheme(
          ThemeData.dark().textTheme,
        ),
        useMaterial3: true,
      ),
      home: UnfocusOnTapOutsideFieldHandler(
        child: const DemoPage(),
      ),
    );
  }
}

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

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  List<String> _todoItems = [
    'Buy groceries for the week',
    'Complete Flutter project documentation',
    'Schedule dentist appointment',
  ];

  List<String> _notes = [
    'Remember to call Mom',
    'Review pull request #42',
  ];

  bool _isReadOnly = false;

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;

    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: isDark
                ? [
              const Color(0xFF1E1B4B),
              const Color(0xFF312E81),
              const Color(0xFF1E1B4B),
            ]
                : [
              const Color(0xFFF5F3FF),
              const Color(0xFFEDE9FE),
              const Color(0xFFF5F3FF),
            ],
          ),
        ),
        child: SafeArea(
          child: CustomScrollView(
            slivers: [
              SliverToBoxAdapter(
                child: Padding(
                  padding: const EdgeInsets.all(24.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // Header
                      Row(
                        children: [
                          Container(
                            padding: const EdgeInsets.all(12),
                            decoration: BoxDecoration(
                              color: colorScheme.primaryContainer,
                              borderRadius: BorderRadius.circular(16),
                            ),
                            child: Icon(
                              Icons.format_list_numbered_rounded,
                              color: colorScheme.onPrimaryContainer,
                              size: 28,
                            ),
                          ),
                          const SizedBox(width: 16),
                          Expanded(
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  'Multiline Prefix',
                                  style: GoogleFonts.spaceGrotesk(
                                    fontSize: 28,
                                    fontWeight: FontWeight.bold,
                                    color: colorScheme.onSurface,
                                  ),
                                ),
                                Text(
                                  'TextField Demo',
                                  style: GoogleFonts.spaceGrotesk(
                                    fontSize: 28,
                                    fontWeight: FontWeight.bold,
                                    color: colorScheme.primary,
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'A numbered list text input with automatic line management',
                        style: TextStyle(
                          fontSize: 14,
                          color: colorScheme.onSurface.withValues(alpha: 0.7),
                        ),
                      ),
                      const SizedBox(height: 32),

                      // Read-only toggle
                      _buildCard(
                        colorScheme: colorScheme,
                        child: Row(
                          children: [
                            Icon(
                              _isReadOnly ? Icons.lock_rounded : Icons.edit_rounded,
                              color: colorScheme.primary,
                            ),
                            const SizedBox(width: 12),
                            Expanded(
                              child: Text(
                                'Read-only Mode',
                                style: TextStyle(
                                  fontSize: 16,
                                  fontWeight: FontWeight.w600,
                                  color: colorScheme.onSurface,
                                ),
                              ),
                            ),
                            Switch.adaptive(
                              value: _isReadOnly,
                              onChanged: (value) {
                                setState(() => _isReadOnly = value);
                              },
                            ),
                          ],
                        ),
                      ),
                      const SizedBox(height: 24),

                      // Todo List Section
                      _buildSectionHeader(
                        icon: Icons.check_circle_outline_rounded,
                        title: 'Todo List',
                        subtitle: 'Press Enter to add new items',
                        colorScheme: colorScheme,
                      ),
                      const SizedBox(height: 12),
                      _buildCard(
                        colorScheme: colorScheme,
                        child: MultilinePrefixTextField(
                          descriptionList: _todoItems,
                          onFinishEditing: (items) {
                            setState(() => _todoItems = items);
                          },
                          isReadOnly: _isReadOnly,
                        ),
                      ),
                      const SizedBox(height: 24),

                      // Notes Section
                      _buildSectionHeader(
                        icon: Icons.note_alt_outlined,
                        title: 'Quick Notes',
                        subtitle: 'Backspace at start to merge lines',
                        colorScheme: colorScheme,
                      ),
                      const SizedBox(height: 12),
                      _buildCard(
                        colorScheme: colorScheme,
                        child: MultilinePrefixTextField(
                          descriptionList: _notes,
                          onFinishEditing: (items) {
                            setState(() => _notes = items);
                          },
                          isReadOnly: _isReadOnly,
                        ),
                      ),
                      const SizedBox(height: 24),

                      // Output Preview Section
                      _buildSectionHeader(
                        icon: Icons.data_object_rounded,
                        title: 'Current Data',
                        subtitle: 'Real-time list output',
                        colorScheme: colorScheme,
                      ),
                      const SizedBox(height: 12),
                      _buildCard(
                        colorScheme: colorScheme,
                        backgroundColor: isDark
                            ? const Color(0xFF1E1E2E)
                            : const Color(0xFFF8FAFC),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              'Todo Items:',
                              style: GoogleFonts.firaCode(
                                fontSize: 12,
                                color: colorScheme.primary,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              _todoItems.toString(),
                              style: GoogleFonts.firaCode(
                                fontSize: 12,
                                color: colorScheme.onSurface.withValues(alpha: 0.8),
                              ),
                            ),
                            const SizedBox(height: 16),
                            Text(
                              'Notes:',
                              style: GoogleFonts.firaCode(
                                fontSize: 12,
                                color: colorScheme.primary,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              _notes.toString(),
                              style: GoogleFonts.firaCode(
                                fontSize: 12,
                                color: colorScheme.onSurface.withValues(alpha: 0.8),
                              ),
                            ),
                          ],
                        ),
                      ),
                      const SizedBox(height: 32),

                      // Instructions
                      Container(
                        padding: const EdgeInsets.all(16),
                        decoration: BoxDecoration(
                          color: colorScheme.primaryContainer.withValues(alpha: 0.3),
                          borderRadius: BorderRadius.circular(16),
                          border: Border.all(
                            color: colorScheme.primary.withValues(alpha: 0.2),
                          ),
                        ),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              children: [
                                Icon(
                                  Icons.lightbulb_outline_rounded,
                                  color: colorScheme.primary,
                                  size: 20,
                                ),
                                const SizedBox(width: 8),
                                Text(
                                  'How to use',
                                  style: TextStyle(
                                    fontSize: 14,
                                    fontWeight: FontWeight.bold,
                                    color: colorScheme.primary,
                                  ),
                                ),
                              ],
                            ),
                            const SizedBox(height: 12),
                            _buildInstruction(
                              '↵',
                              'Press Enter to create a new line',
                              colorScheme,
                            ),
                            _buildInstruction(
                              '⌫',
                              'Backspace at line start to merge with previous',
                              colorScheme,
                            ),
                            _buildInstruction(
                              '🔢',
                              'Line numbers update automatically',
                              colorScheme,
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildSectionHeader({
    required IconData icon,
    required String title,
    required String subtitle,
    required ColorScheme colorScheme,
  }) {
    return Row(
      children: [
        Icon(icon, color: colorScheme.primary, size: 20),
        const SizedBox(width: 8),
        Text(
          title,
          style: TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
            color: colorScheme.onSurface,
          ),
        ),
        const SizedBox(width: 8),
        Text(
          '• $subtitle',
          style: TextStyle(
            fontSize: 12,
            color: colorScheme.onSurface.withValues(alpha: 0.5),
          ),
        ),
      ],
    );
  }

  Widget _buildCard({
    required ColorScheme colorScheme,
    required Widget child,
    Color? backgroundColor,
  }) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: backgroundColor ?? colorScheme.surface,
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: colorScheme.shadow.withValues(alpha: 0.08),
            blurRadius: 24,
            offset: const Offset(0, 8),
          ),
        ],
      ),
      child: child,
    );
  }

  Widget _buildInstruction(
      String symbol,
      String text,
      ColorScheme colorScheme,
      ) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        children: [
          Container(
            width: 28,
            height: 28,
            alignment: Alignment.center,
            decoration: BoxDecoration(
              color: colorScheme.surface,
              borderRadius: BorderRadius.circular(6),
            ),
            child: Text(
              symbol,
              style: TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.bold,
                color: colorScheme.onSurface,
              ),
            ),
          ),
          const SizedBox(width: 12),
          Expanded(
            child: Text(
              text,
              style: TextStyle(
                fontSize: 13,
                color: colorScheme.onSurface.withValues(alpha: 0.8),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
155
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter widget that provides a multiline text field with automatic numbered prefixes (1., 2., 3.), ideal for lists, todos, and structured text input.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on multiline_prefix_text_field