image_form 0.1.1 copy "image_form: ^0.1.1" to clipboard
image_form: ^0.1.1 copied to clipboard

A production-ready Flutter package for single and multiple image form fields. Supports local and network images, cropping, caching, validation, ChangeNotifier controllers, tap-to-view, customizable sh [...]

example/lib/main.dart

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

void main() => runApp(const ImageFormExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'image_form examples',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      home: const ExampleHome(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('image_form examples')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _ExampleTile(
            title: 'Trainer / Student Avatar',
            subtitle: 'Circle · crop · tap to view',
            page: const AvatarExample(),
          ),
          _ExampleTile(
            title: 'Course Thumbnail',
            subtitle: 'Rounded rect · 16:9 crop · cache',
            page: const ThumbnailExample(),
          ),
          _ExampleTile(
            title: 'Organization Logo',
            subtitle: 'Square · no crop · replaceable',
            page: const LogoExample(),
          ),
          _ExampleTile(
            title: 'Certificate Background',
            subtitle: 'Rectangle · free crop · full-width',
            page: const CertificateExample(),
          ),
          _ExampleTile(
            title: 'Play Store Screenshots',
            subtitle: 'Multi image · grid · reorder · remove',
            page: const MultiImageExample(),
          ),
          _ExampleTile(
            title: 'Full Form with Validation',
            subtitle: 'Required · size limit · Form.validate()',
            page: const FormValidationExample(),
          ),
          _ExampleTile(
            title: 'With Controller',
            subtitle: 'ChangeNotifier · dirty check · reset',
            page: const ControllerExample(),
          ),
        ],
      ),
    );
  }
}

// ── 1. Avatar ─────────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    return _ExampleScaffold(
      title: 'Trainer Avatar',
      child: Center(
        child: ImageFormField(
          initialUrl: 'https://picsum.photos/seed/trainer/200',
          config: const ImageFieldConfig(
            shape: ImageFieldShape.circle,
            width: 120,
            height: 120,
            enableCrop: true,
            cropAspectRatio: CropAspectRatioPreset.square,
            cropStyle: CropStyle.circle,
            enableCache: true,
            enableTapToView: true,
            editIconPosition: EditIconPosition.bottomRight,
          ),
          decoration: ImageFieldDecoration(
            border: Border.all(
              color: Theme.of(context).colorScheme.primary,
              width: 2,
            ),
            label: const Text('Profile Photo'),
          ),
          validator: ImageValidators.required(
            message: 'A profile photo is required.',
          ),
          onChanged: (item) => debugPrint('Avatar changed: $item'),
        ),
      ),
    );
  }
}

// ── 2. Course Thumbnail ───────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    return _ExampleScaffold(
      title: 'Course Thumbnail',
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: ImageFormField(
          initialUrl: 'https://picsum.photos/seed/course/640/360',
          config: ImageFieldConfig(
            shape: ImageFieldShape.roundedRect,
            width: MediaQuery.of(context).size.width - 48,
            aspectRatio: 16 / 9,
            borderRadius: 16,
            enableCrop: true,
            cropAspectRatio: CropAspectRatioPreset.ratio16x9,
            enableCache: true,
            enableTapToView: true,
            editIconPosition: EditIconPosition.bottomRight,
          ),
          decoration: ImageFieldDecoration(
            label: const Text('Course Thumbnail'),
            helperText: 'Recommended: 1280 × 720 px (16:9)',
            placeholder: const _PlaceholderContent(
              icon: Icons.video_camera_back_outlined,
              label: 'Add thumbnail',
            ),
          ),
          validator: ImageValidators.compose([
            ImageValidators.required(message: 'Please add a course thumbnail.'),
            ImageValidators.maxFileSizeBytes(
              5 * 1024 * 1024,
              message: 'Thumbnail must be under 5 MB.',
            ),
          ]),
        ),
      ),
    );
  }
}

// ── 3. Org Logo ───────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    return _ExampleScaffold(
      title: 'Organization Logo',
      child: Center(
        child: ImageFormField(
          initialUrl: 'https://picsum.photos/seed/logo/200/200',
          config: const ImageFieldConfig(
            shape: ImageFieldShape.square,
            width: 100,
            height: 100,
            enableCrop: false,
            enableCache: true,
            enableTapToView: true,
            allowRemove: true,
            editIconPosition: EditIconPosition.bottomCenter,
          ),
          decoration: const ImageFieldDecoration(
            label: Text('Organization Logo'),
            helperText: 'Square image works best',
          ),
        ),
      ),
    );
  }
}

// ── 4. Certificate Background ─────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width - 32;

    return _ExampleScaffold(
      title: 'Certificate Background',
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: ImageFormField(
          initialUrl: 'https://picsum.photos/seed/cert/800/566',
          config: ImageFieldConfig(
            allowGallery: false,
            shape: ImageFieldShape.rectangle,
            allowRemove: false,
            enableEditOnTap: true,
            width: width,
            aspectRatio: 800 / 566, // A4 landscape
            enableCrop: true,
            cropAspectRatio: CropAspectRatioPreset.ratio4x3,
            enableCache: true,
            enableTapToView: true,
            editIconPosition: EditIconPosition.bottomRight,
            fit: BoxFit.cover,
          ),
          decoration: const ImageFieldDecoration(
            label: Text('Certificate Background'),
            helperText: 'Landscape format recommended',
            placeholder: _PlaceholderContent(
              icon: Icons.image_outlined,
              label: 'Add background image',
            ),
          ),
        ),
      ),
    );
  }
}

// ── 5. Multi Image (Play Store screenshots) ────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    return _ExampleScaffold(
      title: 'App Screenshots',
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: MultiImageFormField(
          initialUrls: [
            'https://picsum.photos/seed/s1/400/800',
            'https://picsum.photos/seed/s2/400/800',
            'https://picsum.photos/seed/s3/400/800',
          ],
          minCount: 2,
          maxCount: 8,
          layout: MultiImageLayout.grid,
          gridCrossAxisCount: 3,
          itemHeight: 110,
          allowReorder: true,
          config: const ImageFieldConfig(
            shape: ImageFieldShape.roundedRect,
            borderRadius: 10,
            enableCrop: true,
            cropAspectRatio: CropAspectRatioPreset.ratio3x2,
            enableCache: true,
            enableTapToView: true,
            editIconPosition: EditIconPosition.bottomRight,
          ),
          decoration: const ImageFieldDecoration(
            label: Text('App Screenshots'),
            helperText: 'Add 2–8 screenshots. Long-press to reorder.',
          ),
          validator: ImageValidators.multi(
            minCount: 2,
            maxCount: 8,
            minMessage: 'Please add at least 2 screenshots.',
            maxMessage: 'You can upload up to 8 screenshots.',
            perItem: ImageValidators.maxFileSizeBytes(
              10 * 1024 * 1024,
              message: 'Each screenshot must be under 10 MB.',
            ),
          ),
          onChanged: (items) =>
              debugPrint('Screenshots: ${items.length} items'),
        ),
      ),
    );
  }
}

// ── 6. Full Form + Validation ─────────────────────────────────────────────────

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

  @override
  State<FormValidationExample> createState() => _FormValidationExampleState();
}

class _FormValidationExampleState extends State<FormValidationExample> {
  final _formKey = GlobalKey<FormState>();

  void _submit() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(const SnackBar(content: Text('Form is valid ✓')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return _ExampleScaffold(
      title: 'Form with Validation',
      child: Form(
        key: _formKey,
        autovalidateMode: AutovalidateMode.onUserInteraction,
        child: ListView(
          padding: const EdgeInsets.all(20),
          children: [
            // Avatar field
            ImageFormField(
              config: const ImageFieldConfig(
                shape: ImageFieldShape.circle,
                width: 100,
                height: 100,
                enableCrop: true,
              ),
              decoration: const ImageFieldDecoration(
                label: Text('Profile Photo *'),
              ),
              validator: ImageValidators.required(),
            ),

            const SizedBox(height: 24),

            // Thumbnail field
            ImageFormField(
              config: ImageFieldConfig(
                shape: ImageFieldShape.roundedRect,
                width: MediaQuery.of(context).size.width - 40,
                aspectRatio: 16 / 9,
                enableCrop: true,
                cropAspectRatio: CropAspectRatioPreset.ratio16x9,
              ),
              decoration: const ImageFieldDecoration(
                label: Text('Cover Image *'),
                helperText: 'Max 3 MB',
              ),
              validator: ImageValidators.compose([
                ImageValidators.required(message: 'Cover image is required.'),
                ImageValidators.maxFileSizeBytes(3 * 1024 * 1024),
                ImageValidators.allowedExtensions(['jpg', 'jpeg', 'png']),
              ]),
            ),

            const SizedBox(height: 32),

            FilledButton(onPressed: _submit, child: const Text('Submit')),

            const SizedBox(height: 12),

            OutlinedButton(
              onPressed: () => _formKey.currentState!.reset(),
              child: const Text('Reset'),
            ),
          ],
        ),
      ),
    );
  }
}

// ── 7. Controller Example ─────────────────────────────────────────────────────

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

  @override
  State<ControllerExample> createState() => _ControllerExampleState();
}

class _ControllerExampleState extends State<ControllerExample> {
  late final ImageFieldController _controller;

  @override
  void initState() {
    super.initState();
    _controller = ImageFieldController(
      initialItem: const NetworkImageItem(
        url: 'https://picsum.photos/seed/ctrl/200',
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return _ExampleScaffold(
      title: 'Controller Example',
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            // Widget driven by controller
            ImageFormField(
              controller: _controller,
              config: const ImageFieldConfig(
                shape: ImageFieldShape.circle,
                width: 120,
                height: 120,
                enableCrop: true,
                enableTapToView: true,
              ),
            ),

            const SizedBox(height: 24),

            // Listen to controller with ValueListenableBuilder
            ValueListenableBuilder<ImageItem>(
              valueListenable: _controller,
              builder: (context, item, _) {
                return Column(
                  children: [
                    Text(
                      'Current: ${item.runtimeType}',
                      style: Theme.of(context).textTheme.bodyMedium,
                    ),
                    const SizedBox(height: 4),
                    Text(
                      'Has changed: ${_controller.hasChanged}',
                      style: Theme.of(context).textTheme.bodySmall?.copyWith(
                        color: _controller.hasChanged
                            ? Colors.orange
                            : Colors.green,
                      ),
                    ),
                  ],
                );
              },
            ),

            const SizedBox(height: 24),

            // Programmatic actions
            Wrap(
              spacing: 8,
              runSpacing: 8,
              alignment: WrapAlignment.center,
              children: [
                OutlinedButton.icon(
                  icon: const Icon(Icons.link, size: 16),
                  label: const Text('Load new URL'),
                  onPressed: () => _controller.setNetworkUrl(
                    'https://picsum.photos/seed/${DateTime.now().millisecond}/200',
                  ),
                ),
                OutlinedButton.icon(
                  icon: const Icon(Icons.delete_outline, size: 16),
                  label: const Text('Clear'),
                  onPressed: _controller.clear,
                ),
                FilledButton.icon(
                  icon: const Icon(Icons.refresh, size: 16),
                  label: const Text('Reset'),
                  onPressed: _controller.reset,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ── Shared helper widgets ─────────────────────────────────────────────────────

class _ExampleScaffold extends StatelessWidget {
  const _ExampleScaffold({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: child,
    );
  }
}

class _ExampleTile extends StatelessWidget {
  const _ExampleTile({
    required this.title,
    required this.subtitle,
    required this.page,
  });

  final String title;
  final String subtitle;
  final Widget page;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.only(bottom: 10),
      child: ListTile(
        title: Text(title),
        subtitle: Text(subtitle),
        trailing: const Icon(Icons.chevron_right),
        onTap: () => Navigator.push(
          context,
          MaterialPageRoute<void>(builder: (_) => page),
        ),
      ),
    );
  }
}

class _PlaceholderContent extends StatelessWidget {
  const _PlaceholderContent({required this.icon, required this.label});

  final IconData icon;
  final String label;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, size: 36, color: Theme.of(context).colorScheme.outline),
        const SizedBox(height: 8),
        Text(
          label,
          style: Theme.of(context).textTheme.bodySmall?.copyWith(
            color: Theme.of(context).colorScheme.outline,
          ),
        ),
      ],
    );
  }
}
0
likes
140
points
11
downloads

Documentation

API reference

Publisher

verified publishersanamtamang.com.np

Weekly Downloads

A production-ready Flutter package for single and multiple image form fields. Supports local and network images, cropping, caching, validation, ChangeNotifier controllers, tap-to-view, customizable shapes, and editable overlays.

Repository (GitHub)
View/report issues

Topics

#flutter #image-picker #form #image #crop

License

unknown (license)

Dependencies

cached_network_image, flutter, flutter_cache_manager, image_cropper, image_picker, path_provider

More

Packages that depend on image_form