From 640c04d628a036a2be5c81c69e3111533b63241f Mon Sep 17 00:00:00 2001 From: xxparthparekhxx Date: Sun, 7 Aug 2022 14:29:04 +0530 Subject: [PATCH] added Folder support --- lib/firebase_options.dart | 1 - lib/main.dart | 3 +- lib/pages/project_control/list_of_files.dart | 333 +++++++++++++++--- .../list_of_user_projects.dart | 7 +- lib/providers/openprovider.dart | 39 +- 5 files changed, 328 insertions(+), 55 deletions(-) delete mode 100644 lib/firebase_options.dart diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart deleted file mode 100644 index 8b13789..0000000 --- a/lib/firebase_options.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/main.dart b/lib/main.dart index c276efc..2d090ed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ -import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:opencloud/pages/auth/login.dart'; import 'package:opencloud/pages/project_control/add_project.dart'; @@ -41,7 +40,7 @@ class MyApp extends StatelessWidget { }, onGenerateRoute: (settings) { if (settings.name == ListOFProjectFiles.routeName) { - final args = settings.arguments as FirebaseApp; + final args = settings.arguments as ListOFProjectFilesData; return MaterialPageRoute( builder: (context) { return ListOFProjectFiles(args); diff --git a/lib/pages/project_control/list_of_files.dart b/lib/pages/project_control/list_of_files.dart index c38ac58..8e59e46 100644 --- a/lib/pages/project_control/list_of_files.dart +++ b/lib/pages/project_control/list_of_files.dart @@ -10,9 +10,15 @@ import 'package:opencloud/pages/project_control/widgets/file_type.dart'; import 'package:opencloud/providers/openprovider.dart'; import 'package:provider/provider.dart'; -class ListOFProjectFiles extends StatefulWidget { +class ListOFProjectFilesData { final FirebaseApp app; - const ListOFProjectFiles(this.app, {Key? key}) : super(key: key); + final String path; + ListOFProjectFilesData(this.app, this.path); +} + +class ListOFProjectFiles extends StatefulWidget { + final ListOFProjectFilesData data; + const ListOFProjectFiles(this.data, {Key? key}) : super(key: key); static const String routeName = '/ListOFProjectFiles'; @override @@ -28,18 +34,19 @@ class _ListOFProjectFilesState extends State { // TODO: implement initState super.initState(); Timer(Duration.zero, () async { - _listResult = await Provider.of(context, listen: false) - .listAllTheFilesOfAProject(widget.app); + await reload(); setState(() {}); }); } Future reload() async { _listResult = await Provider.of(context, listen: false) - .listAllTheFilesOfAProject(widget.app); + .listAllTheFilesOfAProject(widget.data.app, widget.data.path); setState(() {}); } + addToParent(List a) => waitingForUpload.addAll(a); + @override Widget build(BuildContext context) { var tasks = Provider.of(context).uploadingTasks; @@ -53,11 +60,140 @@ class _ListOFProjectFilesState extends State { }(); } } + return _listResult != null + ? ListFiles( + projectId: widget.data.app.options.projectId, + app: widget.data.app, + reload: reload, + addToParent: addToParent, + listResult: _listResult!, + path: widget.data.path, + ) + : const Center(child: CircularProgressIndicator()); + } +} + +class ListFiles extends StatefulWidget { + final String projectId; + final ListResult listResult; + final FirebaseApp app; + final Function reload; + final Function addToParent; + final String path; + + const ListFiles( + {Key? key, + required this.projectId, + required this.app, + required this.reload, + required this.addToParent, + required this.listResult, + required this.path}) + : super(key: key); + + @override + State createState() => _ListFilesState(); +} + +class _ListFilesState extends State { + bool multiSelecting = false; + List selected = []; + + startMultiSelect(ele) { + //if not multiSelecting then start multiSelecting + if (!multiSelecting) { + multiSelecting = true; + } + //add the prefix to the selected list + if (!selected.contains(ele)) { + selected.add(ele); + } + setState(() {}); + } + + select(bool Selected, Reference ele) { + if (Selected) { + selected.remove(ele); + if (selected.isEmpty) { + multiSelecting = false; + } + } else { + if (multiSelecting) { + selected.add(ele); + } + } + setState(() {}); + } + + //write a recursive function to delete all the files in the selected + deleteFolder(Reference ref) async { + print(ref.fullPath); + var list = await ref.listAll(); + print(list.prefixes); + //list.items are the files in the current folder + //list.prefix in the are folders inside the current folder + for (var i = 0; i < list.items.length; i++) { + await list.items[i].delete(); + } + for (var i = 0; i < list.prefixes.length; i++) { + await deleteFolder(list.prefixes[i]); + } + } + + bool Loading = false; + + @override + Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(widget.app.options.projectId), + leading: multiSelecting + ? IconButton( + icon: const Icon(Icons.close), + onPressed: () { + setState(() { + multiSelecting = false; + selected.clear(); + }); + }, + ) + : null, + title: Text(multiSelecting + ? "${selected.length} ${selected.length == 1 ? 'item' : 'items'} " + : widget.projectId), actions: [ - if (Provider.of(context).uploadingTasks.isNotEmpty) + if (multiSelecting) + IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + setState(() { + Loading = true; + }); + + Iterable deletes = selected.map((e) { + if (selected[0].fullPath.split('/').last.split(".").length == + 1) { + return deleteFolder(e); + } else { + return e.delete(); + } + }); + + for (var e in deletes) { + await e; + } + setState(() { + Loading = false; + }); + setState(() { + Loading = false; + multiSelecting = false; + selected.clear(); + }); + await widget.reload(); + }, + ), + if (Provider.of(context).uploadingTasks.isNotEmpty && + !multiSelecting) IconButton( icon: const Icon(Icons.cloud_upload_sharp), onPressed: () { @@ -68,48 +204,157 @@ class _ListOFProjectFilesState extends State { ), body: Column( children: [ - if (_listResult != null) - for (var ele in _listResult!.items) - ListTile( - leading: FileTypeIcon(extension: ele.name.split(".").last), - title: Text(ele.name), - trailing: IconButton( + if (Loading) const LinearProgressIndicator(), + Expanded( + child: ListView( + children: [ + for (var ele in widget.listResult.prefixes) + (ele) { + bool sel = selected.contains(ele); + return ListTile( + leading: sel + ? const Icon( + Icons.verified, + color: Colors.blue, + ) + : const Icon(Icons.folder_open_sharp), + title: Text(ele.name), + selected: sel, + selectedColor: Colors.blue, + onLongPress: () => startMultiSelect(ele), + onTap: multiSelecting + ? () => select(sel, ele) + : () { + Navigator.of(context).pushNamed( + ListOFProjectFiles.routeName, + arguments: ListOFProjectFilesData( + widget.app, ele.fullPath)); + }, + ); + }(ele), + for (var ele in widget.listResult.items) + (ele) { + bool sel = selected.contains(ele); + + return ListTile( + leading: sel + ? const Icon( + Icons.verified, + color: Colors.blue, + ) + : FileTypeIcon(extension: ele.name.split(".").last), + title: Text(ele.name), + onTap: () => select(sel, ele), + selected: sel, + selectedColor: Colors.blue, + onLongPress: () => startMultiSelect(ele), + trailing: IconButton( + onPressed: () async { + showModalBottomSheet( + context: context, + builder: (c) { + return FileOptions( + ele: ele, + app: widget.app, + reload: widget.reload, + ); + }); + }, + icon: const Icon(Icons.more_vert), + ), + ); + }(ele) + ], + ), + ), + ], + ), + floatingActionButton: multiSelecting + ? null + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + child: const Icon(Icons.create_new_folder_outlined), + onPressed: () { + //create a alert dialog to ask the user if they want to create a folder + + showDialog( + context: context, + builder: (context) { + return NewFileAlertDialog(widget: widget); + }); + }), + FloatingActionButton( + child: const Icon(Icons.upload_file), onPressed: () async { - showModalBottomSheet( - context: context, - builder: (c) { - return FileOptions( - ele: ele, - app: widget.app, - reload: reload, - ); - }); + FilePickerResult? re = await FilePicker.platform.pickFiles( + allowCompression: false, + allowMultiple: true, + ); + if (re != null) { + List files = re.files; + widget.addToParent( + await Provider.of(context, listen: false) + .uploadFileToProject( + app: widget.app, + uploadpath: widget.path, + paths: files.map((e) => e.path!).toList())); + } }, - icon: const Icon(Icons.more_vert), ), - // onTap: () async { - // await Navigator.of(context).pushNamed( - // ListOFProjectFiles.routeName, - // arguments: ele); - // }, - ) - ], - ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.upload_file), - onPressed: () async { - FilePickerResult? re = await FilePicker.platform.pickFiles( - allowCompression: false, - allowMultiple: false, - ); - if (re != null) { - PlatformFile file = re.files.first; - waitingForUpload.add( - await Provider.of(context, listen: false) - .uploadFileToProject(app: widget.app, path: file.path!)); - } + ], + ), + ); + } +} + +class NewFileAlertDialog extends StatefulWidget { + const NewFileAlertDialog({ + Key? key, + required this.widget, + }) : super(key: key); + + final ListFiles widget; + + @override + State createState() => _NewFileAlertDialogState(); +} + +class _NewFileAlertDialogState extends State { + String folderName = ""; + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text("Create Folder"), + content: TextField( + onChanged: (value) { + folderName = value; }, + decoration: const InputDecoration( + labelText: "Folder Name", border: OutlineInputBorder()), ), + actions: [ + ElevatedButton( + child: const Text("Cancel"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ElevatedButton( + child: const Text("Create"), + onPressed: () async { + if (folderName.isNotEmpty) { + await Provider.of(context, listen: false).createFolder( + path: widget.widget.path, + name: folderName, + proj: widget.widget.app); + } + await widget.widget.reload(); + Navigator.of(context).pop(); + }, + ), + ], ); } } diff --git a/lib/pages/project_control/list_of_user_projects.dart b/lib/pages/project_control/list_of_user_projects.dart index bd3de08..822056e 100644 --- a/lib/pages/project_control/list_of_user_projects.dart +++ b/lib/pages/project_control/list_of_user_projects.dart @@ -17,14 +17,17 @@ class ListOfProjects extends StatelessWidget { itemCount: Provider.of(context).Projects.length, itemBuilder: (context, index) { return ListTile( + leading: const Icon(Icons.work_outlined), title: Text(Provider.of(context) .Projects[index] .options .projectId), onTap: () { Navigator.of(context).pushNamed(ListOFProjectFiles.routeName, - arguments: Provider.of(context, listen: false) - .Projects[index]); + arguments: ListOFProjectFilesData( + Provider.of(context, listen: false) + .Projects[index], + "")); }, ); }), diff --git a/lib/providers/openprovider.dart b/lib/providers/openprovider.dart index d5c47b6..b3aefcc 100644 --- a/lib/providers/openprovider.dart +++ b/lib/providers/openprovider.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -158,10 +159,11 @@ class OpenDrive with ChangeNotifier { notifyListeners(); } - Future listAllTheFilesOfAProject(FirebaseApp proj) async { + Future listAllTheFilesOfAProject( + FirebaseApp proj, String? path) async { return FirebaseStorage.instanceFor( app: proj, bucket: proj.options.storageBucket) - .ref() + .ref(path ?? "") .listAll(); } @@ -180,17 +182,42 @@ class OpenDrive with ChangeNotifier { mode: LaunchMode.externalApplication); } - Future uploadFileToProject( - {required FirebaseApp app, required String path}) async { + Future createFolder( + {required String path, + required String name, + required FirebaseApp proj}) async { + await FirebaseStorage.instanceFor( + app: proj, bucket: proj.options.storageBucket) + .ref(path) + .child("$name/NEWFILEPLACEHOLDER.txt") + .putData(Uint8List(0)); + } + + Future> uploadFileToProject( + {required FirebaseApp app, + String? uploadpath, + required List paths}) async { var storage = FirebaseStorage.instanceFor( app: app, bucket: app.options.storageBucket); + List ids = []; + List brr = [ + for (var path in paths) uploads(storage, uploadpath, path, app) + ]; + for (var element in brr) { + ids.add(await element); + } + + return ids; + } + + Future uploads(FirebaseStorage storage, String? uploadpath, String path, + FirebaseApp app) async { UploadTask task = - storage.ref("/${path.split("/").last}").putFile(File(path)); + storage.ref("$uploadpath/${path.split("/").last}").putFile(File(path)); final int id = DateTime.now().microsecondsSinceEpoch; _uploadingTasks.add(UploadMeta(id, task, app.options.projectId)); notifyListeners(); await task.whenComplete(() { - print("uploadcomplete"); _uploadingTasks.removeWhere((element) => element.id == id); notifyListeners(); });