ns_file_coordinator_util 0.22.1 copy "ns_file_coordinator_util: ^0.22.1" to clipboard
ns_file_coordinator_util: ^0.22.1 copied to clipboard

PlatformiOSmacOS

A Flutter plugin to call iOS/macOS `NSFileCoordinator` APIs.

example/lib/main.dart

// ignore_for_file: use_build_context_synchronously

import 'dart:io';

import 'package:accessing_security_scoped_resource/accessing_security_scoped_resource.dart';
import 'package:darwin_url/darwin_url.dart';
import 'package:flutter/material.dart';
import 'package:ios_document_picker/ios_document_picker.dart';
import 'package:ios_document_picker/ios_document_picker_platform_interface.dart';
import 'package:macos_file_picker/macos_file_picker.dart';
import 'package:macos_file_picker/macos_file_picker_platform_interface.dart';
import 'dart:async';

import 'package:ns_file_coordinator_util/ns_file_coordinator_util.dart';
import 'package:ns_file_coordinator_util_example/async_read_route.dart';
import 'package:ns_file_coordinator_util_example/async_write_route.dart';
import 'package:tmp_path/tmp_path.dart';
import 'package:path/path.dart' as p;

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHome(),
    );
  }
}

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

  @override
  State<MyHome> createState() => _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  final _plugin = NsFileCoordinatorUtil();
  final _macosPicker = MacosFilePicker();
  final _iosPicker = IosDocumentPicker();
  final _accessPlugin = AccessingSecurityScopedResource();
  final _darwinUrlPlugin = DarwinUrl();

  late TextEditingController _fileTextController;
  String _output = '';
  String? _icloudFolder;
  bool _overwriteFlag = false;

  @override
  void initState() {
    super.initState();
    _fileTextController = TextEditingController();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: SingleChildScrollView(
        child: GestureDetector(
          onTap: () {
            FocusScope.of(context).requestFocus(FocusNode());
          },
          child: Container(
            padding: const EdgeInsets.all(20),
            child: Column(
              children: [
                const Text('Click the + to select an iCloud folder first'),
                _sep(),
                OutlinedButton(
                    onPressed: () async {
                      if (_icloudFolder == null) {
                        return;
                      }
                      await Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) =>
                                  AsyncReadRoute(dirUrl: _icloudFolder!)));
                    },
                    child: const Text('Read file async tests')),
                _sep(),
                OutlinedButton(
                    onPressed: () async {
                      if (_icloudFolder == null) {
                        return;
                      }
                      await Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) =>
                                  AsyncWriteRoute(dirUrl: _icloudFolder!)));
                    },
                    child: const Text('Write file async tests')),
                _sep(),
                if (_icloudFolder != null)
                  Text('iCloud folder: $_icloudFolder'),
                _sep(),
                ..._renderButtons(),
                _sep(),
                Text(_output)
              ],
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _selectFolder,
        tooltip: 'Select an folder',
        child: const Icon(Icons.add),
      ),
    );
  }

  Widget _sep() {
    return const SizedBox(
      height: 10,
    );
  }

  List<Widget> _renderButtons() {
    if (_icloudFolder == null) {
      return [];
    }
    return [
      const Text('Relative file path'),
      TextField(
        controller: _fileTextController,
      ),
      _sep(),
      OutlinedButton(
          onPressed: _readSync, child: const Text('Read/download file')),
      _sep(),
      OutlinedButton(
          onPressed: () => _readSync(partial: true),
          child: const Text('Read file with offset')),
      _sep(),
      OutlinedButton(onPressed: _stat, child: const Text('Get information')),
      _sep(),
      OutlinedButton(
          onPressed: () => _list(recursive: false),
          child: const Text('List contents')),
      _sep(),
      OutlinedButton(
          onPressed: () => _list(recursive: true),
          child: const Text('List contents recursively')),
      _sep(),
      OutlinedButton(
          onPressed: () => _listFiles(), child: const Text('List files')),
      _sep(),
      OutlinedButton(onPressed: _delete, child: const Text('Delete')),
      _sep(),
      OutlinedButton(onPressed: _move, child: const Text('Move')),
      _sep(),
      OutlinedButton(onPressed: _copyPath, child: const Text('Copy path')),
      _sep(),
      OutlinedButton(onPressed: _exists, child: const Text('Check existence')),
      _sep(),
      OutlinedButton(onPressed: _mkdir, child: const Text('Mkdir')),
      _sep(),
      CheckboxListTile(
        title: const Text('Overwrite flag'),
        value: _overwriteFlag,
        onChanged: (value) {
          setState(() {
            _overwriteFlag = value!;
          });
        },
      ),
      _sep(),
    ];
  }

  Future<void> _selectFolder() async {
    try {
      String? dirUrl;
      if (Platform.isMacOS) {
        var list = await _macosPicker.pick(MacosFilePickerMode.folder);
        if (list == null) {
          dirUrl = null;
        } else {
          dirUrl = list.first.url;
        }
      } else {
        final res = await _iosPicker.pick(DocumentPickerType.directory);
        if (res == null) {
          dirUrl = null;
        } else {
          dirUrl = res.first.url;
        }
      }
      if (dirUrl == null) {
        return;
      }

      var hasAccess = await _accessPlugin
          .startAccessingSecurityScopedResourceWithURL(dirUrl);
      if (!hasAccess) {
        throw 'Failed to gain access to $dirUrl';
      }
      setState(() {
        _icloudFolder = dirUrl;
      });
    } catch (err) {
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _readSync({bool? partial}) async {
    try {
      var dir = _icloudFolder;
      var fileRelPath = _fileTextController.text;
      if (fileRelPath.isEmpty || dir == null) {
        return;
      }
      var fileAbsUrl =
          await _darwinUrlPlugin.append(dir, [fileRelPath], isDir: false);

      setState(() {
        _output = 'Reading/downloading $dir';
      });
      final data = await _plugin.readFileSync(fileAbsUrl,
          start: partial == true ? 3 : null, count: partial == true ? 2 : null);
      setState(() {
        _output = 'File content: ${data.length} bytes';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _list({required bool recursive}) async {
    try {
      var dir = _icloudFolder;
      if (dir == null) {
        return;
      }
      setState(() {
        _output = 'Listing contents of $dir';
      });
      var contents = await _plugin.listContents(dir,
          recursive: recursive, relativePathInfo: true);
      setState(() {
        _output = '--- Contents ---\n${contents.join('\n')}';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _listFiles() async {
    try {
      var dir = _icloudFolder;
      if (dir == null) {
        return;
      }
      setState(() {
        _output = 'Listing content files of $dir';
      });
      var contents = await _plugin.listContentFiles(dir);
      setState(() {
        _output = '--- Contents ---\n${contents.join('\n')}';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _stat() async {
    try {
      var dir = _icloudFolder;
      var fileRelPath = _fileTextController.text;
      if (fileRelPath.isEmpty || dir == null) {
        return;
      }
      var fileAbsUrl =
          await _darwinUrlPlugin.append(dir, [fileRelPath], isDir: false);

      setState(() {
        _output = 'Getting information of $dir';
      });
      var info = await _plugin.stat(fileAbsUrl);
      setState(() {
        _output = info == null ? '<NULL>' : info.fullDescription();
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _delete() async {
    try {
      var dir = _icloudFolder;
      var fileRelPath = _fileTextController.text;
      if (fileRelPath.isEmpty || dir == null) {
        return;
      }
      var fileAbsUrl =
          await _darwinUrlPlugin.append(dir, [fileRelPath], isDir: false);

      setState(() {
        _output = 'Deleting $dir';
      });
      await _plugin.delete(fileAbsUrl);
      setState(() {
        _output = 'Deleted';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _move() async {
    try {
      var dir = _icloudFolder;
      var fileRelPath = _fileTextController.text;
      if (fileRelPath.isEmpty || dir == null) {
        return;
      }
      var fileAbsUrl =
          await _darwinUrlPlugin.append(dir, [fileRelPath], isDir: false);
      var newFileAbsUrl =
          await _darwinUrlPlugin.append(dir, ['newName'], isDir: false);

      setState(() {
        _output = 'Rename $fileAbsUrl to $newFileAbsUrl';
      });
      await _plugin.move(fileAbsUrl, newFileAbsUrl);
      setState(() {
        _output = 'Renamed';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _copyPath() async {
    try {
      var dir = _icloudFolder;
      var fileRelPath = _fileTextController.text;
      if (fileRelPath.isEmpty || dir == null) {
        return;
      }
      var fileAbsUrl =
          await _darwinUrlPlugin.append(dir, [fileRelPath], isDir: false);
      var tmpDir = await _createTempDir();
      var tmpDirUrl = await _darwinUrlPlugin.filePathToUrl(tmpDir);

      setState(() {
        _output = 'Writing to $fileAbsUrl: overwrite=$_overwriteFlag';
      });
      await _plugin.copyPath(tmpDirUrl, fileAbsUrl, overwrite: _overwriteFlag);
      setState(() {
        _output = 'Succeeded';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _exists() async {
    try {
      var dir = _icloudFolder;
      var fileRelPath = _fileTextController.text;
      if (fileRelPath.isEmpty || dir == null) {
        return;
      }
      var fileAbsUrl =
          await _darwinUrlPlugin.append(dir, [fileRelPath], isDir: false);

      setState(() {
        _output = 'Checking if $fileAbsUrl exists';
      });
      var isDir = await _plugin.isDirectory(fileAbsUrl);
      setState(() {
        if (isDir == null) {
          _output = 'Not found';
          return;
        }
        _output = 'Result: ${isDir ? 'is a directory' : 'is a file'}';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _mkdir() async {
    try {
      var dir = _icloudFolder;
      var dirName = _fileTextController.text;
      if (dirName.isEmpty || dir == null) {
        return;
      }

      setState(() {
        _output = 'Creating directory $dirName';
      });
      await _plugin.mkdirp(dir, [dirName]);
      setState(() {
        _output = 'Created';
      });
    } catch (err) {
      setState(() {
        _output = '';
      });
      await _showErrorAlert(context, err.toString());
    }
  }

  Future<void> _showErrorAlert(BuildContext context, String msg) async {
    return showDialog<void>(
      context: context,
      builder: (BuildContext context) => AlertDialog(
        title: const SelectableText('Error'),
        content: SelectableText(msg),
        actions: <Widget>[
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  Future<String> _createTempDir() async {
    var tmpDir = tmpPath();
    await Directory(tmpDir).create();
    await File(p.join(tmpDir, '1.txt')).writeAsString('file 1');
    await File(p.join(tmpDir, '2.txt')).writeAsString('file 2');
    return tmpDir;
  }
}
1
likes
160
points
25
downloads

Publisher

verified publisherflutter-cavalry.com

Weekly Downloads

A Flutter plugin to call iOS/macOS `NSFileCoordinator` APIs.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on ns_file_coordinator_util