reusable_editor 1.3.5 copy "reusable_editor: ^1.3.5" to clipboard
reusable_editor: ^1.3.5 copied to clipboard

A modular form and file editor package for Flutter with support for Form widgets. Includes Cubit-based state management, form field validation, and UI input widgets.

example/main.dart

import 'dart:io';

import 'package:cross_file/cross_file.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:i_validator/i_validator.dart';
import 'package:intl/intl.dart';
import 'package:reusable_editor/reusable_editor.dart';

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

/// Define a sample Enum for the multi-select demo
enum UserRole { admin, editor, viewer, guest }

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Reusable Editor Example',
      home: FormEditorDemo(),
    );
  }
}

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

  @override
  State<FormEditorDemo> createState() => _FormEditorDemoState();
}

class _FormEditorDemoState extends State<FormEditorDemo> {
  /// --- Cubit Instances ---

  final EnumOptionCubit<FileDataSourceType> _sourceCubit = EnumOptionCubit();

  /// Multi-select Cubit
  late final MultiEnumOptionCubit<UserRole> _rolesCubit;

  final FieldCubit<String> _genderCubit = FieldCubit<String>(
    initialValue: 'Male',
    validator: RequiredFieldValidator().call,
  );

  final ImageFieldCubit _imageCubit = ImageFieldCubit();
  final ListImageFieldCubit _listImageCubit = ListImageFieldCubit();
  final AudioFieldCubit _audioCubit = AudioFieldCubit();
  final VideoFieldCubit _videoCubit = VideoFieldCubit();
  final DocFieldCubit _docCubit = DocFieldCubit();

  final ToggleCubit _checkboxCubit = ToggleCubit(
    initialValue: false,
    validator: (val) => val == true ? null : 'You must accept the terms.',
  );

  final TextFieldCubit _textFieldCubit = TextFieldCubit(
    initialValue: '',
    validator: MinLengthValidator(3).call,
  );

  final FieldCubit<DateTime> _datePickerCubit = FieldCubit<DateTime>(
    validator: RequiredFieldValidator().call,
  );

  final FieldCubit<TimeOfDay> _timePickerCubit = FieldCubit<TimeOfDay>(
    validator: RequiredFieldValidator().call,
  );

  final FieldCubit<double> _sliderCubit = FieldCubit<double>(initialValue: 0.5);

  final ToggleCubit _switchCubit = ToggleCubit(
    initialValue: false,
    validator: (val) => val == true ? null : 'Feature must be enabled.',
  );

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

    /// MultiEnumOptionCubit no longer needs options
    _rolesCubit = MultiEnumOptionCubit<UserRole>();
  }

  @override
  void dispose() {
    _checkboxCubit.close();
    _textFieldCubit.close();
    _datePickerCubit.close();
    _timePickerCubit.close();
    _sliderCubit.close();
    _switchCubit.close();
    _sourceCubit.close();
    _imageCubit.close();
    _listImageCubit.close();
    _audioCubit.close();
    _videoCubit.close();
    _docCubit.close();
    _rolesCubit.close();
    _genderCubit.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final dropdownItems = [
      EnumOptionEntity(
        type: FileDataSourceType.fireStorage,
        icon: Icons.cloud,
        label: 'Firebase',
      ),
      EnumOptionEntity(
        type: FileDataSourceType.server,
        icon: Icons.storage,
        label: 'Server',
      ),
    ];

    final roleOptions = [
      EnumOptionEntity(
        type: UserRole.admin,
        label: 'Administrator',
        icon: Icons.security,
      ),
      EnumOptionEntity(
        type: UserRole.editor,
        label: 'Content Editor',
        icon: Icons.edit,
      ),
      EnumOptionEntity(
        type: UserRole.viewer,
        label: 'Viewer Only',
        icon: Icons.visibility,
      ),
      EnumOptionEntity(
        type: UserRole.guest,
        label: 'Guest',
        icon: Icons.person_outline,
      ),
    ];

    return Scaffold(
      appBar: AppBar(title: const Text('Reusable Editor Demo')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Single Selection (Dropdown)',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),

            EnumOptionDropDownMenuFormField<FileDataSourceType>(
              selectedValue: _sourceCubit.state.selectedOption,
              onChanged: _sourceCubit.selectOption,
              hint: 'Choose data source',
              dropdownItems: dropdownItems,
            ),

            const SizedBox(height: 24),

            Text(
              'Multi-Selection (Checkboxes)',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),

            Card(
              child: Padding(
                padding: const EdgeInsets.all(12),
                child: EnumMultiOptionCheckboxGroup<UserRole>(
                  cubit: _rolesCubit,
                  options: roleOptions,
                  label: 'User Roles',
                ),
              ),
            ),

            const SizedBox(height: 24),

            Text(
              'Image Picker',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),

            Wrap(
              spacing: 10,
              children: [
                ElevatedButton.icon(
                  onPressed: () async {
                    final XFile? pickedFile = await 'assets/sample.png'
                        .loadAsXFile();
                    if (pickedFile != null) {
                      _imageCubit.selectImage(pickedFile);
                    }
                  },
                  icon: const Icon(Icons.image),
                  label: const Text('Single Pick'),
                ),
                ElevatedButton.icon(
                  onPressed: () async {
                    // Simulating multiple file selection
                    final results = await Future.wait([
                      'assets/sample.png'.loadAsXFile(),
                      'assets/sample.png'.loadAsXFile(),
                    ]);
                    final pickedFiles = results.whereType<XFile>().toList();

                    _listImageCubit.selectImages(pickedFiles);
                  },
                  icon: const Icon(Icons.photo_library),
                  label: const Text('Multi Pick'),
                ),
                ElevatedButton.icon(
                  onPressed: () async {
                    final XFile? pickedFile = await 'assets/sample.mp3'
                        .loadAsXFile();
                    if (pickedFile != null) {
                      _audioCubit.selectAudio(pickedFile);
                    }
                  },
                  icon: const Icon(Icons.audiotrack),
                  label: const Text('Audio Pick'),
                ),
                ElevatedButton.icon(
                  onPressed: () async {
                    final XFile? pickedFile = await 'assets/sample.mp4'
                        .loadAsXFile();
                    if (pickedFile != null) {
                      _videoCubit.selectVideo(pickedFile);
                    }
                  },
                  icon: const Icon(Icons.video_collection),
                  label: const Text('Video Pick'),
                ),
                ElevatedButton.icon(
                  onPressed: () async {
                    final XFile? pickedFile = await 'assets/sample.pdf'
                        .loadAsXFile();
                    if (pickedFile != null) {
                      _docCubit.selectDoc(pickedFile);
                    }
                  },
                  icon: const Icon(Icons.description),
                  label: const Text('Doc Pick'),
                ),
              ],
            ),

            const SizedBox(height: 16),

            Wrap(
              spacing: 20,
              runSpacing: 20,
              children: [
                _buildUploadPreview(
                  cubit: _imageCubit,
                  label: 'Image',
                  builder: (state) {
                    final XFile? pickedFile = state.pickedFile;
                    if (pickedFile != null) {
                      return ClipRRect(
                        borderRadius: BorderRadius.circular(8),
                        child: Image.file(
                          File(pickedFile.path),
                          height: 80,
                          width: 80,
                          fit: BoxFit.cover,
                        ),
                      );
                    }
                    return const SizedBox(
                      width: 80,
                      height: 80,
                      child: Center(child: Text('No image')),
                    );
                  },
                ),
                _buildUploadPreview(
                  cubit: _audioCubit,
                  label: 'Audio',
                  builder: (state) {
                    return Icon(
                      state.pickedFile != null
                          ? Icons.music_note
                          : Icons.mic_off,
                      size: 80,
                      color: state.pickedFile != null
                          ? Colors.blue
                          : Colors.grey,
                    );
                  },
                ),
                _buildUploadPreview(
                  cubit: _videoCubit,
                  label: 'Video',
                  builder: (state) {
                    return Icon(
                      state.pickedFile != null
                          ? Icons.movie
                          : Icons.videocam_off,
                      size: 80,
                      color: state.pickedFile != null
                          ? Colors.red
                          : Colors.grey,
                    );
                  },
                ),
                _buildUploadPreview(
                  cubit: _docCubit,
                  label: 'Document',
                  builder: (state) {
                    return Icon(
                      state.pickedFile != null
                          ? Icons.insert_drive_file
                          : Icons.file_present,
                      size: 80,
                      color: state.pickedFile != null
                          ? Colors.green
                          : Colors.grey,
                    );
                  },
                ),
              ],
            ),

            const SizedBox(height: 16),

            BlocBuilder<ListImageFieldCubit, ListImageFieldState>(
              bloc: _listImageCubit,
              builder: (context, state) {
                final pickedFiles = state.pickedFiles;
                if (pickedFiles.isNotEmpty) {
                  return Column(
                    children: [
                      Wrap(
                        spacing: 4,
                        children: pickedFiles
                            .map(
                              (file) => ClipRRect(
                                borderRadius: BorderRadius.circular(4),
                                child: Image.file(
                                  File(file.path),
                                  height: 40,
                                  width: 40,
                                  fit: BoxFit.cover,
                                ),
                              ),
                            )
                            .toList(),
                      ),
                      const Text(
                        'Multi Images',
                        style: TextStyle(fontSize: 10),
                      ),
                    ],
                  );
                }
                return const Text('No multi images');
              },
            ),

            const Divider(height: 40),

            Text(
              'Standard Form Fields',
              style: Theme.of(context).textTheme.headlineSmall,
            ),

            const SizedBox(height: 16),

            AppRadioGroup<String>(
              cubit: _genderCubit,
              label: 'Select Gender',
              values: const ['Male', 'Female', 'Other'],
              labels: const ['Male', 'Female', 'Other'],
              activeColor: Colors.deepPurple,
            ),

            const SizedBox(height: 16),

            AppCheckbox(
              cubit: _checkboxCubit,
              label: 'Accept Terms and Conditions',
            ),

            const SizedBox(height: 16),

            AppTextField(
              bloc: _textFieldCubit,
              label: 'Name',
              hintText: 'Enter your name',
            ),

            const SizedBox(height: 16),

            AppDatePicker(
              cubit: _datePickerCubit,
              labelText: 'Select Date',
              placeholderText: 'Tap to pick a date',
              formatter: DateFormat('yyyy-MM-dd'),
            ),

            const SizedBox(height: 16),

            AppTimePicker(
              cubit: _timePickerCubit,
              labelText: 'Select Time',
              hintText: 'Tap to pick a time',
            ),

            const SizedBox(height: 16),

            AppSlider(
              cubit: _sliderCubit,
              min: 0.0,
              max: 1.0,
              divisions: 10,
              labelText: 'Intensity Level',
              displayValue: (val) => '${(val * 100).toInt()}%',
            ),

            const SizedBox(height: 16),

            AppSwitch(cubit: _switchCubit, label: 'Enable Notifications'),

            const SizedBox(height: 32),

            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _validateForm,
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.all(16),
                ),
                child: const Text('Validate & Submit'),
              ),
            ),

            const SizedBox(height: 40),
          ],
        ),
      ),
    );
  }

  void _validateForm() {
    final isValid = [
      _genderCubit.validate(),
      _checkboxCubit.validate(),
      _textFieldCubit.validate(),
      _datePickerCubit.validate(),
      _timePickerCubit.validate(),
      _switchCubit.validate(),
      _imageCubit.validate(),
      _audioCubit.validate(),
      _videoCubit.validate(),
      _docCubit.validate(),
    ].every((error) => error == null);

    final selectedRoles = _rolesCubit.selectedTypes
        .map((e) => e.name)
        .join(', ');

    final message = isValid
        ? 'Valid! Gender: ${_genderCubit.state.value}, Roles: ${selectedRoles.isEmpty ? 'None' : selectedRoles}'
        : 'Please correct the highlighted errors.';

    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text(message)));
  }

  Widget _buildUploadPreview({
    required dynamic cubit,
    required String label,
    required Widget Function(dynamic state) builder,
  }) {
    return Column(
      children: [
        BlocBuilder(
          bloc: cubit,
          builder: (context, state) {
            return builder(state);
          },
        ),
        Text(label, style: const TextStyle(fontSize: 10)),
      ],
    );
  }
}
1
likes
150
points
629
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A modular form and file editor package for Flutter with support for Form widgets. Includes Cubit-based state management, form field validation, and UI input widgets.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

bloc, cross_file, equatable, flutter, flutter_bloc, get_it_di_global_variable, i_validator, intl, path_provider, provider

More

Packages that depend on reusable_editor