vision_gallery_saver 2.0.2 copy "vision_gallery_saver: ^2.0.2" to clipboard
vision_gallery_saver: ^2.0.2 copied to clipboard

A Flutter plugin to save images and videos to the gallery on iOS and Android with support for custom paths, skip duplicates, and various file formats.

example/lib/main.dart

import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:vision_gallery_saver/vision_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  debugPrint('Starting Vision Gallery Saver Example App');
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Vision Gallery Saver',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey _globalKey = GlobalKey();
  String _lastSavedPath = '';
  bool _saveInProgress = false;

  @override
  void initState() {
    super.initState();
    debugPrint('Vision Gallery Saver HomePage initialized');
  }

  void _showSaveResult(Map<dynamic, dynamic> result) {
    debugPrint('Save result: $result');

    if (result['isSuccess']) {
      String? filePath = result['filePath'] ?? result['existingFilePath'];

      if (filePath != null && filePath.isNotEmpty) {
        setState(() {
          _lastSavedPath = filePath;
        });

        if (result['foundExistingFile'] == true) {
          debugPrint('File already exists at: $filePath');
        } else {
          debugPrint('Successfully saved file to: $filePath');
        }

        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(result['foundExistingFile'] == true
                ? 'File already exists: ${filePath.split('/').last}'
                : 'Saved successfully!'),
            duration: const Duration(seconds: 3),
          ),
        );
      }
    } else {
      debugPrint('Save operation failed: ${result['errorMessage']}');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Failed to save: ${result['errorMessage']}'),
          backgroundColor: Colors.red,
        ),
      );
    }

    setState(() {
      _saveInProgress = false;
    });
  }

  Future<void> _saveShieldImage({bool skipIfExists = false}) async {
    if (_saveInProgress) {
      debugPrint('Save operation already in progress. Ignoring request.');
      return;
    }

    setState(() {
      _saveInProgress = true;
    });

    debugPrint('Starting Shield image save...');
    debugPrint('skipIfExists: $skipIfExists');

    try {
      debugPrint('Rendering RepaintBoundary to image');
      RenderRepaintBoundary boundary = _globalKey.currentContext!
          .findRenderObject() as RenderRepaintBoundary;

      debugPrint('Converting boundary to high-resolution image (3.0x)');
      ui.Image image = await boundary.toImage(pixelRatio: 3.0);

      debugPrint('Converting image to PNG byte data');
      ByteData? byteData =
          await image.toByteData(format: ui.ImageByteFormat.png);

      if (byteData != null) {
        debugPrint('Byte data received, size: ${byteData.lengthInBytes} bytes');
        debugPrint(
            'Saving shield image to gallery with custom subfolder "Pictures/Avengers/Shields"');

        final result = await VisionGallerySaver.saveImage(
          byteData.buffer.asUint8List(),
          quality: 100,
          name: "captain_america_shield",
          isReturnImagePathOfIOS: true,
          skipIfExists: skipIfExists,
          androidRelativePath: "Pictures/Avengers/Shields",
        );

        _showSaveResult(result);
      } else {
        debugPrint('Failed to get byte data from image');
        _showSaveResult({
          'isSuccess': false,
          'errorMessage': 'Failed to convert image to bytes'
        });
      }
    } catch (e, stackTrace) {
      debugPrint('Error saving shield image: $e');
      debugPrint('Stack trace: $stackTrace');
      _showSaveResult({'isSuccess': false, 'errorMessage': e.toString()});
    }
  }

  Future<void> _saveIronManImage() async {
    if (_saveInProgress) {
      debugPrint('Save operation already in progress. Ignoring request.');
      return;
    }

    setState(() {
      _saveInProgress = true;
    });

    debugPrint('Starting Iron Man image download and save...');

    try {
      debugPrint('Downloading Iron Man image from network URL');
      ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("Downloading Iron Man image...")));

      debugPrint(
          'Fetching image data from URL: https://i.imgur.com/XcDD7mg.jpeg');
      var response = await Dio().get("https://i.imgur.com/XcDD7mg.jpeg",
          options: Options(responseType: ResponseType.bytes));
      debugPrint('Download complete, received ${response.data.length} bytes');

      debugPrint(
          'Saving Iron Man image to gallery with custom subfolder "Pictures/Avengers/Characters"');
      final result = await VisionGallerySaver.saveImage(
        Uint8List.fromList(response.data),
        quality: 100,
        name: "iron_man",
        isReturnImagePathOfIOS: true,
        skipIfExists: false,
        androidRelativePath: "Pictures/Avengers/Characters",
      );

      _showSaveResult(result);
    } catch (e, stackTrace) {
      debugPrint('Error saving Iron Man image: $e');
      debugPrint('Stack trace: $stackTrace');
      _showSaveResult({'isSuccess': false, 'errorMessage': e.toString()});
    }
  }

  Future<void> _saveThorGif({bool skipIfExists = false}) async {
    if (_saveInProgress) {
      debugPrint('Save operation already in progress. Ignoring request.');
      return;
    }

    setState(() {
      _saveInProgress = true;
    });

    debugPrint('Starting Thor image download and save...');
    debugPrint('skipIfExists: $skipIfExists');

    try {
      debugPrint('Getting temporary directory for download');
      var appDocDir = await getTemporaryDirectory();
      String savePath = "${appDocDir.path}/thor.gif";
      debugPrint('Temp file path: $savePath');

      debugPrint(
          'Downloading Thor image from URL: https://i.imgur.com/yeg063e.jpeg');
      ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("Downloading Thor image...")));

      await Dio().download("https://i.imgur.com/yeg063e.jpeg", savePath);
      debugPrint('Download complete, saved to temporary location');

      debugPrint(
          'Saving Thor image to gallery with custom subfolder "Pictures/Avengers/Characters"');
      final result = await VisionGallerySaver.saveFile(
        savePath,
        name: "thor",
        isReturnPathOfIOS: true,
        skipIfExists: skipIfExists,
        androidRelativePath: "Pictures/Avengers/Characters",
      );

      // Clean up temp file
      debugPrint('Deleting temporary file');
      await File(savePath).delete();
      debugPrint('Temporary file deleted');

      _showSaveResult(result);
    } catch (e, stackTrace) {
      debugPrint('Error saving Thor image: $e');
      debugPrint('Stack trace: $stackTrace');
      _showSaveResult({'isSuccess': false, 'errorMessage': e.toString()});
    }
  }

  Future<void> _saveEndgameTrailer() async {
    if (_saveInProgress) {
      debugPrint('Save operation already in progress. Ignoring request.');
      return;
    }

    setState(() {
      _saveInProgress = true;
    });

    debugPrint('Starting Endgame trailer download and save...');

    try {
      debugPrint('Getting temporary directory for download');
      var appDocDir = await getTemporaryDirectory();
      String savePath = "${appDocDir.path}/endgame.mp4";
      debugPrint('Temp file path: $savePath');

      debugPrint(
          'Downloading Endgame trailer from URL: https://i.imgur.com/68NpINq.mp4');
      ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("Downloading Endgame trailer...")));

      await Dio().download("https://i.imgur.com/68NpINq.mp4", savePath,
          onReceiveProgress: (received, total) {
        if (total != -1) {
          final progress = (received / total * 100).toStringAsFixed(0);
          debugPrint('Download progress: $progress%');
        }
      });
      debugPrint('Download complete, saved to temporary location');

      debugPrint(
          'Saving Endgame trailer to gallery with custom subfolder "Movies/Avengers/Trailers"');
      final result = await VisionGallerySaver.saveFile(
        savePath,
        name: "endgame_trailer",
        isReturnPathOfIOS: true,
        skipIfExists: false,
        androidRelativePath: "Movies/Avengers/Trailers",
      );

      // Clean up temp file
      debugPrint('Deleting temporary file');
      await File(savePath).delete();
      debugPrint('Temporary file deleted');

      _showSaveResult(result);
    } catch (e, stackTrace) {
      debugPrint('Error saving Endgame trailer: $e');
      debugPrint('Stack trace: $stackTrace');
      _showSaveResult({'isSuccess': false, 'errorMessage': e.toString()});
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Vision Gallery Saver'),
      ),
      body: SingleChildScrollView(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // Circular Shield Image
                RepaintBoundary(
                  key: _globalKey,
                  child: Container(
                    width: 250,
                    height: 250,
                    decoration: const BoxDecoration(
                      shape: BoxShape.circle,
                      gradient: RadialGradient(
                        colors: [Colors.blue, Colors.white, Colors.red],
                        stops: [0.2, 0.5, 0.8],
                      ),
                    ),
                    child: const Icon(Icons.shield,
                        size: 150, color: Colors.white),
                  ),
                ),
                const SizedBox(height: 20),

                // Buttons Section
                Wrap(
                  spacing: 10,
                  runSpacing: 10,
                  alignment: WrapAlignment.center,
                  children: [
                    // Shield Buttons
                    ElevatedButton(
                      onPressed: _saveInProgress ? null : _saveShieldImage,
                      child: const Text('Save Shield'),
                    ),
                    ElevatedButton(
                      onPressed: _saveInProgress
                          ? null
                          : () => _saveShieldImage(skipIfExists: true),
                      child: const Text('Save Shield (Skip if Exists)'),
                    ),

                    // Iron Man Button
                    ElevatedButton(
                      onPressed: _saveInProgress ? null : _saveIronManImage,
                      child: const Text('Save Iron Man'),
                    ),

                    // Thor Button
                    ElevatedButton(
                      onPressed: _saveInProgress ? null : _saveThorGif,
                      child: const Text('Save Thor'),
                    ),

                    // Endgame Trailer Button
                    ElevatedButton(
                      onPressed: _saveInProgress ? null : _saveEndgameTrailer,
                      child: const Text('Save Endgame Trailer'),
                    ),
                  ],
                ),

                // Show Loading Indicator
                if (_saveInProgress)
                  const Padding(
                    padding: EdgeInsets.all(16.0),
                    child: CircularProgressIndicator(),
                  ),

                // Show Last Saved Path
                if (_lastSavedPath.isNotEmpty)
                  Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      children: [
                        const Text(
                          'Last saved at:',
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        const SizedBox(height: 4),
                        Text(
                          _lastSavedPath,
                          textAlign: TextAlign.center,
                          style: const TextStyle(fontSize: 12),
                        ),
                      ],
                    ),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
7
likes
150
points
563
downloads

Publisher

verified publisherensembleui.com

Weekly Downloads

A Flutter plugin to save images and videos to the gallery on iOS and Android with support for custom paths, skip duplicates, and various file formats.

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on vision_gallery_saver

Packages that implement vision_gallery_saver