directory_bookmarks 0.1.2 copy "directory_bookmarks: ^0.1.2" to clipboard
directory_bookmarks: ^0.1.2 copied to clipboard

A Flutter plugin for cross-platform directory bookmarking and secure file operations. Provides a consistent API for handling directory access and file operations, with special support for platform-spe [...]

Directory Bookmarks #

A Flutter plugin for cross-platform directory bookmarking and secure file operations. This plugin provides a consistent API for handling directory access and file operations, with special support for platform-specific security features.

Platform Support #

Platform Status Implementation Details
macOS Supported Security-scoped bookmarks for persistent directory access
Android In Development Storage Access Framework (partial implementation)
iOS Planned Will use security-scoped bookmarks
Windows Planned Future implementation
Linux Planned Future implementation

Note: Currently, this package is primarily focused on macOS support. Using it on other platforms will result in unsupported platform errors. We are actively working on expanding platform support.

Features #

  • Secure Directory Access: Platform-specific secure directory access mechanisms
    • macOS: Security-scoped bookmarks
    • Android: Storage Access Framework
  • Directory Bookmarking: Save and restore access to user-selected directories
  • File Operations: Read, write, and list files in bookmarked directories
  • Persistent Access: Maintain access to directories across app restarts
  • Permission Handling: Built-in permission management and verification
  • Resource Management: Automatic cleanup of system resources

Getting Started #

Add the package to your pubspec.yaml:

dependencies:
  directory_bookmarks: ^0.1.0

Platform-Specific Setup #

macOS (Supported)

  1. Enable App Sandbox and required entitlements in your macOS app. Add the following to your entitlements files:
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
  1. Register the plugin in your AppDelegate.swift:
import directory_bookmarks

class AppDelegate: FlutterAppDelegate {
  override func applicationDidFinishLaunching(_ notification: Notification) {
    guard let mainWindow = mainFlutterWindow else { return }
    guard let controller = mainWindow.contentViewController as? FlutterViewController else { return }
    DirectoryBookmarksPlugin.register(with: controller.registrar(forPlugin: "DirectoryBookmarksPlugin"))
    super.applicationDidFinishLaunching(notification)
  }
}

Android (In Development)

Note: Android support is currently in development. The implementation is partial and may not work as expected.

Add the following permissions to your AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Other Platforms (Planned)

Support for iOS, Windows, and Linux is planned for future releases. Using this package on these platforms will currently result in an UnsupportedError.

API Reference #

Directory Bookmark Operations #

  • saveBookmark(String directoryPath, {Map<String, dynamic>? metadata}): Save a directory bookmark with optional metadata
  • resolveBookmark(): Resolve and return the current directory bookmark information

File Operations #

  • saveFile(String fileName, List<int> data): Save raw data to a file in the bookmarked directory
  • saveStringToFile(String fileName, String content): Save text content to a file
  • saveBytesToFile(String fileName, Uint8List bytes): Save binary data to a file
  • readFile(String fileName): Read raw data from a file
  • readStringFromFile(String fileName): Read text content from a file
  • readBytesFromFile(String fileName): Read binary data from a file
  • listFiles(): List all files in the bookmarked directory

Directory Operations #

// Create a directory in the bookmarked location
final success = await DirectoryBookmarkHandler.createDirectory('images');

// Create nested directories
await DirectoryBookmarkHandler.createDirectory('images/thumbnails');

// Save a file in a subdirectory (creates directories if they don't exist)
final imageBytes = await File('path/to/image.jpg').readAsBytes();
await DirectoryBookmarkHandler.saveBytesToFileInPath(
  'images/thumbnails/image1.jpg',
  imageBytes,
);

// Save text file in subdirectory
await DirectoryBookmarkHandler.saveStringToFileInPath(
  'docs/notes/note1.txt',
  'Hello, World!',
);

// List files in a specific subdirectory
final imageFiles = await DirectoryBookmarkHandler.listFilesInPath('images');
if (imageFiles != null) {
  for (final file in imageFiles) {
    print('Image file: $file');
  }
}
class ImageGalleryWithFolders extends StatefulWidget {
  const ImageGalleryWithFolders({super.key});

  @override
  State<ImageGalleryWithFolders> createState() => _ImageGalleryWithFoldersState();
}

class _ImageGalleryWithFoldersState extends State<ImageGalleryWithFolders> {
  String _currentPath = '';
  List<String> _items = [];
  String? _errorMessage;

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

  Future<void> _loadCurrentDirectory() async {
    try {
      final files = await DirectoryBookmarkHandler.listFilesInPath(_currentPath);
      if (files != null) {
        setState(() {
          _items = files;
          _errorMessage = null;
        });
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Error loading directory: $e';
      });
    }
  }

  Future<void> _createNewFolder(String folderName) async {
    try {
      final path = _currentPath.isEmpty 
          ? folderName 
          : '$_currentPath/$folderName';
      
      final success = await DirectoryBookmarkHandler.createDirectory(path);
      if (success) {
        _loadCurrentDirectory();
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Error creating folder: $e';
      });
    }
  }

  Future<void> _navigateToFolder(String folderName) async {
    setState(() {
      _currentPath = _currentPath.isEmpty 
          ? folderName 
          : '$_currentPath/$folderName';
    });
    _loadCurrentDirectory();
  }

  Future<void> _navigateUp() async {
    if (_currentPath.isEmpty) return;
    
    final lastSlash = _currentPath.lastIndexOf('/');
    setState(() {
      _currentPath = lastSlash == -1 ? '' : _currentPath.substring(0, lastSlash);
    });
    _loadCurrentDirectory();
  }

  @override
  Widget build(BuildContext context) {
    if (_errorMessage != null) {
      return Center(child: Text(_errorMessage!));
    }

    return Column(
      children: [
        // Directory navigation bar
        Container(
          padding: const EdgeInsets.all(8),
          child: Row(
            children: [
              IconButton(
                icon: const Icon(Icons.arrow_upward),
                onPressed: _currentPath.isEmpty ? null : _navigateUp,
              ),
              Text('Current path: ${_currentPath.isEmpty ? '/' : _currentPath}'),
              IconButton(
                icon: const Icon(Icons.create_new_folder),
                onPressed: () async {
                  final name = await showDialog<String>(
                    context: context,
                    builder: (context) => NewFolderDialog(),
                  );
                  if (name != null) {
                    _createNewFolder(name);
                  }
                },
              ),
            ],
          ),
        ),
        // Directory contents
        Expanded(
          child: ListView.builder(
            itemCount: _items.length,
            itemBuilder: (context, index) {
              final item = _items[index];
              final isDirectory = !item.contains('.');
              
              return ListTile(
                leading: Icon(isDirectory ? Icons.folder : Icons.image),
                title: Text(item),
                onTap: isDirectory 
                    ? () => _navigateToFolder(item)
                    : null,
              );
            },
          ),
        ),
      ],
    );
  }
}

class NewFolderDialog extends StatelessWidget {
  final controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Create New Folder'),
      content: TextField(
        controller: controller,
        decoration: const InputDecoration(
          labelText: 'Folder Name',
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('Cancel'),
        ),
        TextButton(
          onPressed: () => Navigator.pop(context, controller.text),
          child: const Text('Create'),
        ),
      ],
    );
  }
}

Writing Files #

// Save any type of file (base method)
final fileData = await File('path/to/source/file').readAsBytes();
final success = await DirectoryBookmarkHandler.saveFile(
  'destination.file',
  fileData,
);

// Save text files
final textSuccess = await DirectoryBookmarkHandler.saveStringToFile(
  'example.txt',
  'Hello, World!',
);

// Save binary files (images, PDFs, etc.)
final imageBytes = await File('path/to/image.jpg').readAsBytes();
final imageSuccess = await DirectoryBookmarkHandler.saveBytesToFile(
  'image.jpg',
  imageBytes,
);

Reading Files #

// Read any type of file (base method)
final fileData = await DirectoryBookmarkHandler.readFile('myfile.dat');
if (fileData != null) {
  // Use the file data (List<int>)
}

// Read text files
final textContent = await DirectoryBookmarkHandler.readStringFromFile('example.txt');
if (textContent != null) {
  print('File content: $textContent');
}

// Read binary files
final imageBytes = await DirectoryBookmarkHandler.readBytesFromFile('image.jpg');
if (imageBytes != null) {
  // Use the image bytes (Uint8List)
  final image = Image.memory(imageBytes);
}

Example: Copying a File to Bookmarked Directory #

import 'package:directory_bookmarks/directory_bookmarks.dart';
import 'package:file_picker/file_picker.dart';

Future<void> copyFileToBookmark() async {
  try {
    // Pick a file to copy
    final result = await FilePicker.platform.pickFiles();
    if (result == null) return;

    final file = result.files.first;
    if (file.bytes == null) return;

    // Save to bookmarked directory
    final success = await DirectoryBookmarkHandler.saveBytesToFile(
      file.name,
      file.bytes!,
    );

    if (success) {
      print('File copied successfully');
    } else {
      print('Failed to copy file');
    }
  } catch (e) {
    print('Error copying file: $e');
  }
}

Permission Management #

  • hasWritePermission(): Check if write permission is granted for the bookmarked directory
  • requestWritePermission(): Request write permission for the bookmarked directory

Usage #

Basic Example #

import 'package:directory_bookmarks/directory_bookmarks.dart';

void main() async {
  // Check platform support
  if (!(defaultTargetPlatform == TargetPlatform.macOS ||
        defaultTargetPlatform == TargetPlatform.android)) {
    print('Platform not supported');
    return;
  }

  try {
    // Select and bookmark a directory
    final path = await FilePicker.platform.getDirectoryPath(
      dialogTitle: 'Select a directory to bookmark',
    );
    
    if (path == null) {
      print('No directory selected');
      return;
    }

    // Save the bookmark
    final success = await DirectoryBookmarkHandler.saveBookmark(
      path,
      metadata: {'lastAccessed': DateTime.now().toIso8601String()},
    );

    if (success) {
      print('Directory bookmarked successfully');
    } else {
      print('Failed to bookmark directory');
      return;
    }

    // Resolve the bookmark
    final bookmark = await DirectoryBookmarkHandler.resolveBookmark();
    if (bookmark != null) {
      print('Bookmarked directory: ${bookmark.path}');
      
      // Check write permission
      final hasPermission = await DirectoryBookmarkHandler.hasWritePermission();
      if (!hasPermission) {
        print('No write permission');
        return;
      }

      // List files
      final files = await DirectoryBookmarkHandler.listFiles();
      if (files != null) {
        print('Files in directory: $files');
      }

      // Write a file
      final writeSuccess = await DirectoryBookmarkHandler.saveStringToFile(
        'test.txt',
        'Hello, World!',
      );
      if (writeSuccess) {
        print('File written successfully');
      }

      // Read the file
      final content = await DirectoryBookmarkHandler.readStringFromFile('test.txt');
      if (content != null) {
        print('File content: $content');
      }
    }
  } catch (e) {
    print('Error: $e');
  }
}

Error Handling #

The plugin includes comprehensive error handling:

try {
  // Check platform support first
  if (!(defaultTargetPlatform == TargetPlatform.macOS ||
        defaultTargetPlatform == TargetPlatform.android)) {
    print('Platform ${defaultTargetPlatform.name} is not supported yet');
    return;
  }

  // Try to resolve existing bookmark
  final bookmark = await DirectoryBookmarkHandler.resolveBookmark();
  if (bookmark == null) {
    print('No bookmark found, selecting new directory...');
    
    final path = await FilePicker.platform.getDirectoryPath();
    if (path == null) {
      print('No directory selected');
      return;
    }
    
    final success = await DirectoryBookmarkHandler.saveBookmark(path);
    if (!success) {
      print('Failed to bookmark directory');
      return;
    }
  }

  // Check permissions
  if (!await DirectoryBookmarkHandler.hasWritePermission()) {
    print('No write permission for bookmarked directory');
    return;
  }

  // Perform file operations...
} on PlatformException catch (e) {
  print('Platform error: ${e.message}');
} catch (e) {
  print('Unexpected error: $e');
}

Features and bugs #

Please file feature requests and bugs at the issue tracker.

Contributing #

Contributions are welcome! Please read our contributing guidelines to get started.

License #

This project is licensed under the MIT License - see the LICENSE file for details.

2
likes
130
points
170
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for cross-platform directory bookmarking and secure file operations. Provides a consistent API for handling directory access and file operations, with special support for platform-specific security features.

Repository (GitHub)
View/report issues
Contributing

Topics

#files #storage #directory #bookmarks #security

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

file_picker, flutter, path, path_provider, permission_handler

More

Packages that depend on directory_bookmarks