From 24a49116651c26e548f6769e15921e85c618f9ca Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Fri, 27 Mar 2020 17:33:47 +0100 Subject: [PATCH] Add basic files selector This required to add a platform channel with a method to get the toplevel storage devices. The app will need to request the WRITE_EXTERNAL_STORAGE permission later. Also the dependency on "path" was introduced. --- android/app/src/main/AndroidManifest.xml | 2 + .../kotlin/de/johrpan/musicus/MainActivity.kt | 31 ++- lib/selectors/files.dart | 181 ++++++++++++++++++ pubspec.yaml | 1 + 4 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 lib/selectors/files.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8ec7429..a20ece3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + + if (call.method == "getStorageRoots") { + result.success(getStorageRoots()) + } else { + result.notImplemented() + } + } + } + + private fun getStorageRoots(): ArrayList { + val result = ArrayList() + + ContextCompat.getExternalFilesDirs(this, null).forEach { + val path = it.absolutePath; + val index = path.lastIndexOf("/Android/data/") + + if (index > 0) { + result.add(path.substring(0, index)) + } + } + + return result } } diff --git a/lib/selectors/files.dart b/lib/selectors/files.dart new file mode 100644 index 0000000..8449440 --- /dev/null +++ b/lib/selectors/files.dart @@ -0,0 +1,181 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:path/path.dart' as path; + +class FilesSelector extends StatefulWidget { + @override + _FilesSelectorState createState() => _FilesSelectorState(); +} + +class _FilesSelectorState extends State { + static const platform = MethodChannel('de.johrpan.musicus/platform'); + + List storageRoots = []; + List directories = []; + List contents = []; + Set selectedPaths = {}; + + @override + void initState() { + super.initState(); + + platform.invokeListMethod('getStorageRoots').then((sr) { + setState(() { + storageRoots = sr.map((path) => Directory(path)).toList(); + }); + }); + } + + @override + Widget build(BuildContext context) { + Widget body; + + if (directories.isEmpty) { + if (storageRoots != null) { + body = ListView( + children: storageRoots + .map((dir) => ListTile( + leading: const Icon(Icons.storage), + title: Text(dir.path), + onTap: () { + setState(() { + directories.add(dir); + }); + + openDirectory(dir); + }, + )) + .toList(), + ); + } else { + body = Container(); + } + } else { + if (contents != null) { + body = ListView( + children: contents.map((fse) { + Widget result; + + if (fse is Directory) { + result = ListTile( + leading: const Icon(Icons.folder), + title: Text(path.basename(fse.path)), + onTap: () { + setState(() { + directories.add(fse); + }); + + openDirectory(fse); + }, + ); + } else if (fse is File) { + result = CheckboxListTile( + value: selectedPaths.contains(fse.path), + secondary: Icon(Icons.insert_drive_file), + title: Text(path.basename(fse.path)), + onChanged: (selected) { + setState(() { + if (selected) { + selectedPaths.add(fse.path); + } else { + selectedPaths.remove(fse.path); + } + }); + }, + ); + } + + return result; + }).toList(), + ); + } else { + body = Container(); + } + } + + return WillPopScope( + child: Scaffold( + appBar: AppBar( + title: Text('Choose files'), + actions: [ + FlatButton( + child: Text('DONE'), + onPressed: () { + Navigator.pop(context, selectedPaths); + }, + ), + ], + ), + body: Column( + children: [ + Material( + elevation: 2.0, + child: ListTile( + leading: directories.isNotEmpty ? IconButton( + icon: const Icon(Icons.arrow_upward), + onPressed: up, + ) : null, + title: Text(directories.isEmpty + ? 'Storage devices' + : directories.last.path), + ), + ), + Expanded( + child: body, + ), + ], + ), + ), + onWillPop: () { + if (directories.isNotEmpty) { + up(); + return Future.value(false); + } else { + return Future.value(true); + } + }, + ); + } + + Future openDirectory(Directory directory) async { + setState(() { + contents.clear(); + }); + + final fses = await directory.list().toList(); + fses.sort((fse1, fse2) { + int compareBasenames() => + path.basename(fse1.path).compareTo(path.basename(fse2.path)); + + if (fse1 is Directory) { + if (fse2 is Directory) { + return compareBasenames(); + } else { + return -1; + } + } else if (fse2 is Directory) { + return 1; + } else { + return compareBasenames(); + } + }); + + setState(() { + contents = fses; + }); + } + + void up() { + if (directories.isNotEmpty) { + setState(() { + directories.removeLast(); + }); + + if (directories.isNotEmpty) { + openDirectory(directories.last); + } + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 481f40c..1cc068b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: flutter: sdk: flutter moor_flutter: + path: rxdart: dev_dependencies: