dbas_filesystem 3.1.2 copy "dbas_filesystem: ^3.1.2" to clipboard
dbas_filesystem: ^3.1.2 copied to clipboard

Flutter plugin for cross-platform file system operations with streaming, byte array, and directory support across Android, iOS, macOS, Linux, Windows, and Web (OPFS).

example/lib/main.dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:dbas_filesystem/dbas_filesystem.dart';
import 'package:file_picker/file_picker.dart';
import 'upload_download_io.dart'
  if (dart.library.js_interop) 'upload_download_web.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'DbasFileSystem Example',
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const FileSystemDemo(),
    );
  }
}

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

  @override
  State<FileSystemDemo> createState() => _FileSystemDemoState();
}

class _FileSystemDemoState extends State<FileSystemDemo> {
  DbasFileSystem? _fs;
  String _basePath = '';
  List<_FileEntry> _files = [];
  bool _loading = true;
  String? _error;
  String? _previewContent;
  String? _previewName;

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

  Future<void> _init() async {
    try {
      _fs = await DbasFileSystem.getInstance();
      _basePath = await _fs!.getAppFilePath('');
      await _fs!.createDirectory(_basePath);
      await _refreshFiles();
    } catch (e) {
      setState(() {
        _error = e.toString();
        _loading = false;
      });
    }
  }

  Future<void> _refreshFiles() async {
    if (!mounted) return;
    setState(() => _loading = true);
    try {
      final entries = <_FileEntry>[];
      if (await _fs!.directoryExists(_basePath)) {
        final listed = await _fs!.listDirectory(_basePath);
        for (final entry in listed) {
          final name = entry.path.split('/').last;
          int size = 0;
          await for (final chunk in _fs!.readFileStream(entry.path)) {
            size += chunk.length;
          }
          entries.add(_FileEntry(name: name, path: entry.path, size: size));
        }
        entries.sort((a, b) => a.name.compareTo(b.name));
      }
      if (!mounted) return;
      setState(() {
        _files = entries;
        _loading = false;
        _error = null;
      });
    } catch (e) {
      if (!mounted) return;
      setState(() {
        _error = e.toString();
        _loading = false;
      });
    }
  }

  Future<void> _writeTextFile() async {
    final controller = TextEditingController();
    final nameController = TextEditingController(text: 'example.txt');

    final result = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Write Text File'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(labelText: 'File name'),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: controller,
              decoration: const InputDecoration(labelText: 'Content'),
              maxLines: 5,
            ),
          ],
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
          FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Write')),
        ],
      ),
    );

    if (result != true) return;

    final name = nameController.text.trim();
    if (name.isEmpty) return;

    final bytes = Uint8List.fromList(utf8.encode(controller.text));
    final path = await _fs!.getAppFilePath(name);
    await _fs!.writeFile(path, bytes);
    await _refreshFiles();
  }

  Future<void> _writeBinaryFile() async {
    final nameController = TextEditingController(text: 'data.bin');
    final sizeController = TextEditingController(text: '1024');

    final result = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Write Binary File'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(labelText: 'File name'),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: sizeController,
              decoration: const InputDecoration(labelText: 'Size (bytes)'),
              keyboardType: TextInputType.number,
            ),
          ],
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
          FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Write')),
        ],
      ),
    );

    if (result != true) return;

    final name = nameController.text.trim();
    final size = int.tryParse(sizeController.text.trim()) ?? 0;
    if (name.isEmpty || size <= 0) return;

    final bytes = Uint8List(size);
    for (int i = 0; i < size; i++) {
      bytes[i] = i % 256;
    }

    final path = await _fs!.getAppFilePath(name);
    await _fs!.writeFile(path, bytes);
    await _refreshFiles();
  }

  Future<void> _writeStreamFile() async {
    final nameController = TextEditingController(text: 'streamed.bin');
    final chunksController = TextEditingController(text: '10');
    final chunkSizeController = TextEditingController(text: '1024');

    final result = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Stream Write File'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(labelText: 'File name'),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: chunksController,
              decoration: const InputDecoration(labelText: 'Number of chunks'),
              keyboardType: TextInputType.number,
            ),
            const SizedBox(height: 8),
            TextField(
              controller: chunkSizeController,
              decoration: const InputDecoration(labelText: 'Chunk size (bytes)'),
              keyboardType: TextInputType.number,
            ),
          ],
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
          FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Stream Write')),
        ],
      ),
    );

    if (result != true) return;

    final name = nameController.text.trim();
    final chunks = int.tryParse(chunksController.text.trim()) ?? 0;
    final chunkSize = int.tryParse(chunkSizeController.text.trim()) ?? 0;
    if (name.isEmpty || chunks <= 0 || chunkSize <= 0) return;

    Stream<List<int>> generateChunks() async* {
      for (int i = 0; i < chunks; i++) {
        final chunk = Uint8List(chunkSize);
        for (int j = 0; j < chunkSize; j++) {
          chunk[j] = (i * chunkSize + j) % 256;
        }
        yield chunk;
      }
    }

    final path = await _fs!.getAppFilePath(name);
    await _fs!.writeFileStream(path, generateChunks());
    await _refreshFiles();
  }

  Future<void> _uploadFiles() async {
    final result = await FilePicker.pickFiles(allowMultiple: true, withData: kIsWeb);
    if (result == null || result.files.isEmpty) return;

    int uploaded = 0;
    for (final file in result.files) {
      final destPath = await _fs!.getAppFilePath(file.name);
      final success = await uploadFile(file, destPath, _fs!);
      if (success) uploaded++;
    }

    if (!mounted) return;
    await _refreshFiles();
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Uploaded $uploaded file(s)')),
    );
  }

  Future<void> _downloadFile(_FileEntry entry) async {
    try {
      final content = await _fs!.readFile(entry.path);
      final success = await downloadFile(entry.name, content);
      if (!mounted) return;
      if (success) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Downloaded ${entry.name}')),
        );
      }
    } catch (e) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Download failed: $e')),
      );
    }
  }

  Future<void> _readFile(_FileEntry entry) async {
    final content = await _fs!.readFile(entry.path);

    String preview;
    try {
      preview = utf8.decode(content, allowMalformed: false);
      if (preview.length > 2000) {
        preview = '${preview.substring(0, 2000)}\n... (${content.length} bytes total)';
      }
    } catch (_) {
      final hex = content.take(512).map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
      preview = 'Binary (${content.length} bytes):\n$hex';
      if (content.length > 512) {
        preview += '\n... (${content.length} bytes total)';
      }
    }

    if (!mounted) return;
    setState(() {
      _previewContent = preview;
      _previewName = entry.name;
    });
  }

  Future<void> _readFileStream(_FileEntry entry) async {
    int totalBytes = 0;
    int chunkCount = 0;

    await for (final chunk in _fs!.readFileStream(entry.path)) {
      totalBytes += chunk.length;
      chunkCount++;
    }

    if (!mounted) return;
    setState(() {
      _previewContent = 'Stream read complete:\n'
          '  Chunks: $chunkCount\n'
          '  Total bytes: $totalBytes';
      _previewName = entry.name;
    });
  }

  Future<void> _copyFile(_FileEntry entry) async {
    final nameController = TextEditingController(text: 'copy_${entry.name}');

    final result = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Copy File'),
        content: TextField(
          controller: nameController,
          decoration: const InputDecoration(labelText: 'Destination name'),
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
          FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Copy')),
        ],
      ),
    );

    if (result != true) return;

    final destName = nameController.text.trim();
    if (destName.isEmpty) return;

    final destPath = await _fs!.getAppFilePath(destName);
    await _fs!.copyFile(entry.path, destPath);
    await _refreshFiles();
  }

  Future<void> _deleteFile(_FileEntry entry) async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Delete File'),
        content: Text('Delete "${entry.name}" (${_formatSize(entry.size)})?'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
          FilledButton(
            onPressed: () => Navigator.pop(ctx, true),
            style: FilledButton.styleFrom(backgroundColor: Colors.red),
            child: const Text('Delete'),
          ),
        ],
      ),
    );

    if (confirmed != true) return;

    await _fs!.deleteFile(entry.path);
    if (_previewName == entry.name) {
      setState(() {
        _previewContent = null;
        _previewName = null;
      });
    }
    await _refreshFiles();
  }

  String _formatSize(int bytes) {
    if (bytes < 1024) return '$bytes B';
    if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
    return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('DbasFileSystem Example'),
        actions: [
          IconButton(
            icon: const Icon(Icons.upload_file),
            tooltip: 'Upload files',
            onPressed: _fs != null ? _uploadFiles : null,
          ),
          IconButton(
            icon: const Icon(Icons.refresh),
            tooltip: 'Refresh',
            onPressed: _fs != null ? _refreshFiles : null,
          ),
        ],
      ),
      body: _error != null
          ? Center(child: Text('Error: $_error', style: const TextStyle(color: Colors.red)))
          : _loading
              ? const Center(child: CircularProgressIndicator())
              : _buildBody(),
      floatingActionButton: _fs == null
          ? null
          : Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                FloatingActionButton.small(
                  heroTag: 'stream',
                  onPressed: _writeStreamFile,
                  tooltip: 'Stream write',
                  child: const Icon(Icons.stream),
                ),
                const SizedBox(height: 8),
                FloatingActionButton.small(
                  heroTag: 'binary',
                  onPressed: _writeBinaryFile,
                  tooltip: 'Write binary',
                  child: const Icon(Icons.memory),
                ),
                const SizedBox(height: 8),
                FloatingActionButton(
                  heroTag: 'text',
                  onPressed: _writeTextFile,
                  tooltip: 'Write text file',
                  child: const Icon(Icons.note_add),
                ),
              ],
            ),
    );
  }

  Widget _buildBody() {
    return Column(
      children: [
        Container(
          width: double.infinity,
          padding: const EdgeInsets.all(12),
          color: Theme.of(context).colorScheme.surfaceContainerHighest,
          child: Text(
            'Storage: $_basePath',
            style: Theme.of(context).textTheme.bodySmall,
          ),
        ),
        Expanded(
          flex: 3,
          child: _files.isEmpty
              ? const Center(child: Text('No files. Tap + to create one.'))
              : ListView.builder(
                  itemCount: _files.length,
                  itemBuilder: (context, index) {
                    final entry = _files[index];
                    final isSelected = _previewName == entry.name;
                    return ListTile(
                      leading: Icon(
                        entry.name.endsWith('.txt') ? Icons.description : Icons.insert_drive_file,
                      ),
                      title: Text(entry.name),
                      subtitle: Text(_formatSize(entry.size)),
                      selected: isSelected,
                      trailing: PopupMenuButton<String>(
                        onSelected: (action) {
                          switch (action) {
                            case 'download': _downloadFile(entry);
                            case 'stream': _readFileStream(entry);
                            case 'copy': _copyFile(entry);
                            case 'delete': _deleteFile(entry);
                          }
                        },
                        itemBuilder: (ctx) => [
                          const PopupMenuItem(value: 'download', child: ListTile(leading: Icon(Icons.download), title: Text('Download'))),
                          const PopupMenuItem(value: 'stream', child: ListTile(leading: Icon(Icons.stream), title: Text('Stream Read'))),
                          const PopupMenuItem(value: 'copy', child: ListTile(leading: Icon(Icons.copy), title: Text('Copy'))),
                          const PopupMenuItem(value: 'delete', child: ListTile(leading: Icon(Icons.delete), title: Text('Delete'))),
                        ],
                      ),
                      onTap: () => _readFile(entry),
                    );
                  },
                ),
        ),
        if (_previewContent != null) ...[
          const Divider(height: 1),
          Expanded(
            flex: 2,
            child: Container(
              width: double.infinity,
              padding: const EdgeInsets.all(12),
              color: Theme.of(context).colorScheme.surfaceContainerLow,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Expanded(
                        child: Text(
                          _previewName ?? '',
                          style: Theme.of(context).textTheme.titleSmall,
                        ),
                      ),
                      IconButton(
                        icon: const Icon(Icons.close, size: 18),
                        onPressed: () => setState(() {
                          _previewContent = null;
                          _previewName = null;
                        }),
                      ),
                    ],
                  ),
                  const SizedBox(height: 4),
                  Expanded(
                    child: SingleChildScrollView(
                      child: SelectableText(
                        _previewContent!,
                        style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ],
    );
  }
}

class _FileEntry {
  final String name;
  final String path;
  final int size;

  _FileEntry({required this.name, required this.path, required this.size});
}
1
likes
0
points
240
downloads

Publisher

verified publisherdailysoftwaresystems.com

Weekly Downloads

Flutter plugin for cross-platform file system operations with streaming, byte array, and directory support across Android, iOS, macOS, Linux, Windows, and Web (OPFS).

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_web_plugins, path, path_provider, web

More

Packages that depend on dbas_filesystem

Packages that implement dbas_filesystem