file_compression_plus 1.0.0 copy "file_compression_plus: ^1.0.0" to clipboard
file_compression_plus: ^1.0.0 copied to clipboard

Compress images and PDFs in Flutter. Hit an exact target file size, batch compress with progress, convert JPG PNG WebP HEIC, and shrink PDF documents.

example/lib/main.dart

import 'dart:io';

import 'package:file_compression_plus/file_compression_plus.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'File Compression Plus',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const CompressionDemoPage(),
    );
  }
}

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

  @override
  State<CompressionDemoPage> createState() => _CompressionDemoPageState();
}

class _CompressionDemoPageState extends State<CompressionDemoPage> {
  static const _anyGroup = XTypeGroup(
    label: 'Images and PDF',
    extensions: ['jpg', 'jpeg', 'png', 'webp', 'heic', 'pdf'],
  );

  double _quality = 80;
  double _targetMb = 1;
  PdfCompressionLevel _pdfLevel = PdfCompressionLevel.best;
  bool _isBusy = false;
  double? _batchProgress;
  CompressionResult? _result;
  BatchCompressionResult? _batchResult;
  String? _error;

  /// Compresses one file, letting the package pick image or PDF by extension.
  Future<void> _compressOne() async {
    final picked = await openFile(acceptedTypeGroups: [_anyGroup]);
    if (picked == null) return;

    await _run(() => FileCompressor.compress(
          file: File(picked.path),
          quality: _quality.round(),
          pdfCompressionLevel: _pdfLevel,
        ));
  }

  /// Compresses an image down to a byte budget, the usual upload-limit case.
  Future<void> _compressToTarget() async {
    final picked = await openFile(acceptedTypeGroups: [
      const XTypeGroup(
        label: 'Images',
        extensions: ['jpg', 'jpeg', 'png', 'webp', 'heic'],
      )
    ]);
    if (picked == null) return;

    await _run(() => FileCompressor.compressImageToTargetSize(
          file: File(picked.path),
          targetBytes: (_targetMb * 1024 * 1024).round(),
        ));
  }

  /// Compresses many files at once and follows along with a progress bar.
  Future<void> _compressBatch() async {
    final picked = await openFiles(acceptedTypeGroups: [_anyGroup]);
    if (picked.isEmpty) return;

    setState(() {
      _isBusy = true;
      _result = null;
      _batchResult = null;
      _error = null;
      _batchProgress = 0;
    });

    try {
      final batch = await FileCompressor.compressBatch(
        files: picked.map((file) => File(file.path)).toList(),
        quality: _quality.round(),
        pdfCompressionLevel: _pdfLevel,
        onProgress: (progress) {
          if (mounted) setState(() => _batchProgress = progress.fraction);
        },
      );
      if (!mounted) return;
      setState(() => _batchResult = batch);
    } catch (error) {
      if (!mounted) return;
      setState(() => _error = '$error');
    } finally {
      if (mounted) {
        setState(() {
          _isBusy = false;
          _batchProgress = null;
        });
      }
    }
  }

  /// Runs one compression job and moves the screen through busy, done, error.
  Future<void> _run(Future<CompressionResult> Function() job) async {
    setState(() {
      _isBusy = true;
      _result = null;
      _batchResult = null;
      _error = null;
    });

    try {
      final result = await job();
      if (!mounted) return;
      setState(() => _result = result);
    } catch (error) {
      if (!mounted) return;
      setState(() => _error = '$error');
    } finally {
      if (mounted) setState(() => _isBusy = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;

    return Scaffold(
      appBar: AppBar(
        title: const Text('File Compression Plus'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text('Image quality: ${_quality.round()}',
              style: textTheme.titleMedium),
          Slider(
            value: _quality,
            min: 10,
            max: 100,
            divisions: 18,
            label: '${_quality.round()}',
            onChanged:
                _isBusy ? null : (value) => setState(() => _quality = value),
          ),
          Text('PDF compression level', style: textTheme.titleMedium),
          const SizedBox(height: 8),
          SegmentedButton<PdfCompressionLevel>(
            segments: const [
              ButtonSegment(
                  value: PdfCompressionLevel.none, label: Text('None')),
              ButtonSegment(
                  value: PdfCompressionLevel.normal, label: Text('Normal')),
              ButtonSegment(
                  value: PdfCompressionLevel.best, label: Text('Best')),
            ],
            selected: {_pdfLevel},
            onSelectionChanged: _isBusy
                ? null
                : (selection) => setState(() => _pdfLevel = selection.first),
          ),
          const SizedBox(height: 20),
          FilledButton.icon(
            onPressed: _isBusy ? null : _compressOne,
            icon: const Icon(Icons.compress),
            label: const Text('Compress one file'),
          ),
          const SizedBox(height: 12),
          FilledButton.tonalIcon(
            onPressed: _isBusy ? null : _compressBatch,
            icon: const Icon(Icons.library_add_check_outlined),
            label: const Text('Compress many files'),
          ),
          const Divider(height: 40),
          Text('Target size: ${_targetMb.toStringAsFixed(1)} MB',
              style: textTheme.titleMedium),
          Slider(
            value: _targetMb,
            min: 0.1,
            max: 5,
            divisions: 49,
            label: '${_targetMb.toStringAsFixed(1)} MB',
            onChanged:
                _isBusy ? null : (value) => setState(() => _targetMb = value),
          ),
          FilledButton.tonalIcon(
            onPressed: _isBusy ? null : _compressToTarget,
            icon: const Icon(Icons.straighten),
            label: const Text('Compress an image to that size'),
          ),
          const SizedBox(height: 24),
          if (_isBusy)
            _BusyIndicator(progress: _batchProgress)
          else ...[
            if (_error != null) _ErrorCard(message: _error!),
            if (_result != null) _ResultCard(result: _result!),
            if (_batchResult != null) _BatchCard(batch: _batchResult!),
          ],
        ],
      ),
    );
  }
}

class _BusyIndicator extends StatelessWidget {
  const _BusyIndicator({this.progress});

  final double? progress;

  @override
  Widget build(BuildContext context) {
    if (progress == null) {
      return const Center(child: CircularProgressIndicator());
    }
    return Column(
      children: [
        LinearProgressIndicator(value: progress),
        const SizedBox(height: 8),
        Text('${(progress! * 100).round()} percent'),
      ],
    );
  }
}

class _ResultCard extends StatelessWidget {
  const _ResultCard({required this.result});

  final CompressionResult result;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              result.wasSkipped ? 'Already small enough' : 'Done',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            Text('Before: ${result.readableOriginalSize}'),
            Text('After: ${result.readableCompressedSize}'),
            Text('Saved: ${result.savedPercentage.toStringAsFixed(1)} percent'),
            const SizedBox(height: 8),
            SelectableText(result.path),
          ],
        ),
      ),
    );
  }
}

class _BatchCard extends StatelessWidget {
  const _BatchCard({required this.batch});

  final BatchCompressionResult batch;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Batch finished',
                style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 8),
            Text('${batch.successes.length} compressed, '
                '${batch.failures.length} failed'),
            Text('Before: ${formatBytes(batch.totalOriginalSize)}'),
            Text('After: ${formatBytes(batch.totalCompressedSize)}'),
            Text('Saved: ${formatBytes(batch.totalSavedBytes)} '
                '(${batch.savedPercentage.toStringAsFixed(1)} percent)'),
            for (final failure in batch.failures) ...[
              const SizedBox(height: 8),
              Text('Failed: ${failure.file.path}',
                  style: TextStyle(color: Theme.of(context).colorScheme.error)),
            ],
          ],
        ),
      ),
    );
  }
}

class _ErrorCard extends StatelessWidget {
  const _ErrorCard({required this.message});

  final String message;

  @override
  Widget build(BuildContext context) {
    return Card(
      color: Theme.of(context).colorScheme.errorContainer,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(message),
      ),
    );
  }
}
4
likes
160
points
158
downloads

Documentation

Documentation
API reference

Publisher

verified publishertisankan.dev

Weekly Downloads

Compress images and PDFs in Flutter. Hit an exact target file size, batch compress with progress, convert JPG PNG WebP HEIC, and shrink PDF documents.

Repository (GitHub)
View/report issues

Topics

#compression #image #pdf #file #optimization

License

MIT (license)

Dependencies

flutter, flutter_image_compress, path, path_provider, syncfusion_flutter_pdf

More

Packages that depend on file_compression_plus