From 08bbeb26191abd560e6a251d5adc1fd7f74e7732 Mon Sep 17 00:00:00 2001 From: RafayAhmad7548 Date: Thu, 14 Aug 2025 07:07:50 +0500 Subject: [PATCH 1/4] provider yayy --- lib/sftp_connection_list.dart | 7 +- lib/sftp_explorer.dart | 213 ++++++++++++----------------- lib/sftp_provider.dart | 57 ++++++++ lib/widgets/operation_buttons.dart | 25 ++-- pubspec.lock | 16 +++ pubspec.yaml | 1 + 6 files changed, 180 insertions(+), 139 deletions(-) create mode 100644 lib/sftp_provider.dart diff --git a/lib/sftp_connection_list.dart b/lib/sftp_connection_list.dart index ca4d4fb..2514458 100644 --- a/lib/sftp_connection_list.dart +++ b/lib/sftp_connection_list.dart @@ -4,8 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:fluxcloud/connection.dart'; import 'package:fluxcloud/sftp_explorer.dart'; +import 'package:fluxcloud/sftp_provider.dart'; import 'package:fluxcloud/sftp_worker.dart'; import 'package:fluxcloud/widgets/add_server_modal.dart'; +import 'package:provider/provider.dart'; class SftpConnectionList extends StatefulWidget { const SftpConnectionList({ @@ -80,7 +82,10 @@ class _SftpConnectionListState extends State { onTap: () async { final sftpWorker = await SftpWorker.spawn(_connections[index]); if (context.mounted) { - Navigator.push(context, MaterialPageRoute(builder: (context) => SftpExplorer(sftpWorker: sftpWorker,))); + Navigator.push(context, MaterialPageRoute(builder: (context) => ChangeNotifierProvider( + create: (_) => SftpProvider(sftpWorker), + child: SftpExplorer() + ))); } }, borderRadius: BorderRadius.circular(10), diff --git a/lib/sftp_explorer.dart b/lib/sftp_explorer.dart index 00d6948..7c4f4b6 100644 --- a/lib/sftp_explorer.dart +++ b/lib/sftp_explorer.dart @@ -1,54 +1,16 @@ import 'dart:io'; -import 'package:dartssh2/dartssh2.dart'; import 'package:file_picker/file_picker.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:fluxcloud/main.dart'; -import 'package:fluxcloud/sftp_worker.dart'; +import 'package:fluxcloud/sftp_provider.dart'; +import 'package:provider/provider.dart'; import 'widgets/operation_buttons.dart'; -class SftpExplorer extends StatefulWidget { - const SftpExplorer({super.key, required this.sftpWorker}); - - final SftpWorker sftpWorker; - - @override - State createState() => _SftpExplorerState(); -} - -class _SftpExplorerState extends State { - - String path = '/'; - - bool _isLoading = true; - late List _dirContents; - - double? _uploadProgress; - double? _downloadProgress; - - void _setDownloadProgress(double? progress) => setState(() => _downloadProgress = progress); - - @override - void initState() { - super.initState(); - _listDir(); - } - - Future _listDir() async { - setState(() => _isLoading = true); - try { - _dirContents = await widget.sftpWorker.listdir(path); - } - catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString())); - } - } - setState(() => _isLoading = false); - } - +class SftpExplorer extends StatelessWidget { + const SftpExplorer({super.key}); @override Widget build(BuildContext context) { @@ -60,52 +22,54 @@ class _SftpExplorerState extends State { actionsPadding: EdgeInsets.only(right: 20), leading: IconButton( onPressed: () { - if (path == '/') { + if (context.read().path == '/') { // TODO: figure this out // Navigator.pop(context); } else { - path = path.substring(0, path.length - 1); - path = path.substring(0, path.lastIndexOf('/')+1); - _listDir(); + context.read().goToPrevDir(); } }, icon: Icon(Icons.arrow_back) ), actions: [ - if (_uploadProgress != null) - Stack( - alignment: Alignment.center, - children: [ - TweenAnimationBuilder( - tween: Tween(begin: 0, end: _uploadProgress), - duration: Duration(milliseconds: 300), - builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) - ), - IconButton( - onPressed: () { - // TODO: show upload details here - }, - icon: Icon(Icons.upload) - ), - ] + Selector( + selector: (_, sftpProvider) => sftpProvider.uploadProgress, + builder: (_, uploadProgress, __) => uploadProgress != null ? Stack( + alignment: Alignment.center, + children: [ + TweenAnimationBuilder( + tween: Tween(begin: 0, end: uploadProgress), + duration: Duration(milliseconds: 300), + builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) + ), + IconButton( + onPressed: () { + // TODO: show upload details here + }, + icon: Icon(Icons.upload) + ), + ] + ) : const SizedBox.shrink(), ), - if (_downloadProgress != null) - Stack( - alignment: Alignment.center, - children: [ - TweenAnimationBuilder( - tween: Tween(begin: 0, end: _downloadProgress), - duration: Duration(milliseconds: 300), - builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) - ), - IconButton( - onPressed: () { - // TODO: show donwload details here - }, - icon: Icon(Icons.download) - ), - ] + Selector( + selector: (_, sftpProvider) => sftpProvider.downloadProgress, + builder: (_, downloadProgress, __) => downloadProgress != null ? Stack( + alignment: Alignment.center, + children: [ + TweenAnimationBuilder( + tween: Tween(begin: 0, end: downloadProgress), + duration: Duration(milliseconds: 300), + builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) + ), + IconButton( + onPressed: () { + // TODO: show donwload details here + }, + icon: Icon(Icons.download) + ), + ] + ) : const SizedBox.shrink(), ), ], ), @@ -113,48 +77,47 @@ class _SftpExplorerState extends State { body: PopScope( canPop: false, onPopInvokedWithResult: (_, _) { - if (path != '/') { - path = path.substring(0, path.length - 1); - path = path.substring(0, path.lastIndexOf('/')+1); - _listDir(); + if (context.read().path != '/') { + context.read().goToPrevDir(); } }, - child: AnimatedSwitcher( - duration: Duration(milliseconds: 300), - transitionBuilder: (child, animation) { - final curved = CurvedAnimation( - parent: animation, - curve: Curves.fastOutSlowIn - ); - return FadeTransition( - opacity: curved, - child: ScaleTransition( - scale: Tween( - begin: 0.92, - end: 1 - ).animate(curved), - child: child, - ), - ); - }, - child: _isLoading ? Center(child: CircularProgressIndicator()) : ListView.builder( - key: ValueKey(path), - itemCount: _dirContents.length, - itemBuilder: (context, index) { - final dirEntry = _dirContents[index]; - return ListTile( - leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description), - title: Text(dirEntry.filename), - trailing: OperationButtons(sftpWorker: widget.sftpWorker, path: path, dirEntries: [dirEntry], listDir: _listDir, setDownloadProgress: _setDownloadProgress,), - onTap: () { - if (dirEntry.attr.isDirectory) { - path = '$path${dirEntry.filename}/'; - _listDir(); - } - }, + child: Consumer( + builder: (_, sftpProvider, __) => AnimatedSwitcher( + duration: Duration(milliseconds: 300), + transitionBuilder: (child, animation) { + final curved = CurvedAnimation( + parent: animation, + curve: Curves.fastOutSlowIn ); - }, - ) + return FadeTransition( + opacity: curved, + child: ScaleTransition( + scale: Tween( + begin: 0.92, + end: 1 + ).animate(curved), + child: child, + ), + ); + }, + child: sftpProvider.isLoading ? Center(child: CircularProgressIndicator()) : ListView.builder( + key: ValueKey(sftpProvider.path), + itemCount: sftpProvider.dirContents.length, + itemBuilder: (context, index) { + final dirEntry = sftpProvider.dirContents[index]; + return ListTile( + leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description), + title: Text(dirEntry.filename), + trailing: OperationButtons(dirEntries: [dirEntry],), + onTap: () { + if (dirEntry.attr.isDirectory) { + sftpProvider.goToDir('${sftpProvider.path}${dirEntry.filename}/'); + } + }, + ); + }, + ) + ), ), ) ); @@ -183,9 +146,10 @@ class _SftpExplorerState extends State { TextButton(onPressed: () => Navigator.pop(context), child: Text('Cancel')), TextButton( onPressed: () async { + final sftpProvider = context.read(); try { - await widget.sftpWorker.mkdir('$path${nameController.text}'); - _listDir(); + await sftpProvider.sftpWorker.mkdir('${sftpProvider.path}${nameController.text}'); + sftpProvider.listDir(); } catch (e) { if (context.mounted) { @@ -207,6 +171,7 @@ class _SftpExplorerState extends State { FloatingActionButton( heroTag: 'upload-file', onPressed: () async { + final sftpProvider = context.read(); final List filePaths; if (Platform.isAndroid | Platform.isIOS) { final res = await FilePicker.platform.pickFiles(allowMultiple: true); @@ -218,10 +183,10 @@ class _SftpExplorerState extends State { } for (final filePath in filePaths) { try { - await for (final progress in widget.sftpWorker.uploadFile(path, filePath)) { - setState(() => _uploadProgress = progress); + await for (final progress in sftpProvider.sftpWorker.uploadFile(sftpProvider.path, filePath)) { + sftpProvider.setUploadProgress(progress); } - await _listDir(); + await sftpProvider.listDir(); } catch (e) { if (context.mounted) { @@ -229,8 +194,8 @@ class _SftpExplorerState extends State { } } } - setState(() => _uploadProgress = null); - _listDir(); + sftpProvider.setUploadProgress(null); + sftpProvider.listDir(); }, child: Icon(Icons.upload), ), diff --git a/lib/sftp_provider.dart b/lib/sftp_provider.dart new file mode 100644 index 0000000..1059298 --- /dev/null +++ b/lib/sftp_provider.dart @@ -0,0 +1,57 @@ +import 'package:dartssh2/dartssh2.dart'; +import 'package:flutter/material.dart'; +import 'package:fluxcloud/sftp_worker.dart'; + +class SftpProvider extends ChangeNotifier { + final SftpWorker _sftpWorker; + + String _path = '/'; + bool _isLoading = false; + late List _dirContents; + + double? _uploadProgress; + double? _downloadProgress; + + SftpProvider(this._sftpWorker) { + listDir(); + } + + SftpWorker get sftpWorker => _sftpWorker; + + String get path => _path; + bool get isLoading => _isLoading; + List get dirContents => _dirContents; + + double? get uploadProgress => _uploadProgress; + double? get downloadProgress => _downloadProgress; + + Future listDir() async { + _isLoading = true; + notifyListeners(); + _dirContents = await _sftpWorker.listdir(_path); + _isLoading = false; + notifyListeners(); + } + + void goToPrevDir() { + _path = _path.substring(0, _path.length - 1); + _path = _path.substring(0, _path.lastIndexOf('/')+1); + listDir(); + } + + void goToDir(String path) { + _path = path; + listDir(); + } + + void setUploadProgress(double? progress) { + _uploadProgress = progress; + notifyListeners(); + } + + void setDownloadProgress(double? progress) { + _downloadProgress = progress; + notifyListeners(); + } + +} diff --git a/lib/widgets/operation_buttons.dart b/lib/widgets/operation_buttons.dart index c720a79..578657b 100644 --- a/lib/widgets/operation_buttons.dart +++ b/lib/widgets/operation_buttons.dart @@ -1,23 +1,20 @@ import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/material.dart'; import 'package:fluxcloud/main.dart'; -import 'package:fluxcloud/sftp_worker.dart'; +import 'package:fluxcloud/sftp_provider.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; class OperationButtons extends StatelessWidget { const OperationButtons({ - super.key, - required this.sftpWorker, required this.path, required this.dirEntries, required this.listDir, required this.setDownloadProgress, + super.key, required this.dirEntries, }); - final SftpWorker sftpWorker; - final String path; final List dirEntries; - final Function listDir; - final Function(double? progress) setDownloadProgress; @override Widget build(BuildContext context) { + final sftpProvider = context.read(); return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -54,8 +51,8 @@ class OperationButtons extends StatelessWidget { TextButton( onPressed: () async { try { - await sftpWorker.rename('$path${dirEntry.filename}', '$path${newNameController.text}'); - listDir(); + await sftpProvider.sftpWorker.rename('${sftpProvider.path}${dirEntry.filename}', '${sftpProvider.path}${newNameController.text}'); + sftpProvider.listDir(); } on SftpStatusError catch (e) { if (context.mounted) { @@ -97,14 +94,14 @@ class OperationButtons extends StatelessWidget { onPressed: () async { for (final dirEntry in dirEntries) { try { - await sftpWorker.remove(dirEntry, path); + await sftpProvider.sftpWorker.remove(dirEntry, sftpProvider.path); } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString())); } } - listDir(); + sftpProvider.listDir(); if (context.mounted) { Navigator.pop(context); } @@ -125,8 +122,8 @@ class OperationButtons extends StatelessWidget { if (downloadsDir == null) return; for (final dirEntry in dirEntries) { try { - await for (final progress in sftpWorker.downloadFile(dirEntry, path, downloadsDir.path)) { - setDownloadProgress(progress); + await for (final progress in sftpProvider.sftpWorker.downloadFile(dirEntry, sftpProvider.path, downloadsDir.path)) { + sftpProvider.setDownloadProgress(progress); } } catch (e) { @@ -135,7 +132,7 @@ class OperationButtons extends StatelessWidget { } } } - setDownloadProgress(null); + sftpProvider.setDownloadProgress(null); }, icon: Icon(Icons.download) ) diff --git a/pubspec.lock b/pubspec.lock index 2a4a94f..7089f8a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -320,6 +320,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: "direct main" description: @@ -408,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.9.1" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 6b69008..8b93912 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: path: ^1.9.1 file_picker: ^10.2.0 path_provider: ^2.1.5 + provider: ^6.1.5 dev_dependencies: flutter_test: From 1ccadcf2008304cef76efaed35a5ec497ea70907 Mon Sep 17 00:00:00 2001 From: RafayAhmad7548 Date: Thu, 14 Aug 2025 07:34:45 +0500 Subject: [PATCH 2/4] make upload/download work sequentialy so animation stays cool --- lib/sftp_worker.dart | 115 ++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/lib/sftp_worker.dart b/lib/sftp_worker.dart index 0221c94..c6aaaae 100644 --- a/lib/sftp_worker.dart +++ b/lib/sftp_worker.dart @@ -109,6 +109,66 @@ class SftpWorker { static void _sftpCmdHandler(SendPort sendPort, ReceivePort receivePort, SftpClient sftpClient) { + + final StreamController<(int, DownloadFile)> downloadController = StreamController(); + downloadController.stream.asyncMap((cmd) async { + final (int id, DownloadFile downloadCmd) = cmd; + try { + final localFile = File('${downloadCmd.downloadPath}/${downloadCmd.file.filename}'); + if (await localFile.exists()) { + sendPort.send((id, RemoteError('File Already Exists', ''))); + return; + } + final localFileWriter = await localFile.open(mode: FileMode.write); + final remoteFile = await sftpClient.open('${downloadCmd.path}${downloadCmd.file.filename}'); + final fileSize = downloadCmd.file.attr.size!; + bool timeout = true; + await for (final bytes in remoteFile.read( + onProgress: (progress) { + if (timeout) { + timeout = false; + sendPort.send((id, progress/fileSize)); + Future.delayed(Duration(seconds: 2), () => timeout = true); + } + } + )) { + await localFileWriter.writeFrom(bytes); + } + } + on SftpStatusError catch (e) { + sendPort.send((id, RemoteError(e.message, ''))); + } + sendPort.send((id, 1.0)); + }).listen((_) {}); + + final StreamController<(int, UploadFile)> uploadController = StreamController(); + uploadController.stream.asyncMap((cmd) async { + final (int id, UploadFile uploadCmd) = cmd; + try { + final file = File(uploadCmd.filePath); + final fileSize = await file.length(); + final remoteFile = await sftpClient.open( + '${uploadCmd.path}${basename(uploadCmd.filePath)}', + mode: SftpFileOpenMode.create | SftpFileOpenMode.write | SftpFileOpenMode.exclusive + ); + bool timeout = true; + await remoteFile.write( + file.openRead().cast(), + onProgress: (progress) { + if (timeout) { + timeout = false; + sendPort.send((id, progress/fileSize)); + Future.delayed(Duration(seconds: 2), () => timeout = true); + } + } + ); + } + on SftpStatusError catch (e) { + sendPort.send((id, RemoteError(e.message, ''))); + } + sendPort.send((id, 1.0)); + }).listen((_) {}); + receivePort.listen((message) async { final (int id, SftpCommand command) = message; switch (command) { @@ -120,30 +180,8 @@ class SftpWorker { on SftpStatusError catch (e) { sendPort.send((id, RemoteError(e.message, ''))); } - case UploadFile(:final path, :final filePath): - try { - final file = File(filePath); - final fileSize = await file.length(); - final remoteFile = await sftpClient.open( - '$path${basename(filePath)}', - mode: SftpFileOpenMode.create | SftpFileOpenMode.write | SftpFileOpenMode.exclusive - ); - bool timeout = true; - await remoteFile.write( - file.openRead().cast(), - onProgress: (progress) { - if (timeout) { - timeout = false; - sendPort.send((id, progress/fileSize)); - Future.delayed(Duration(seconds: 2), () => timeout = true); - } - } - ); - } - on SftpStatusError catch (e) { - sendPort.send((id, RemoteError(e.message, ''))); - } - sendPort.send((id, 1.0)); + case UploadFile(): + uploadController.add((id, command)); case MkDir(:final path): try { await sftpClient.mkdir(path); @@ -187,33 +225,8 @@ class SftpWorker { on SftpStatusError catch (e) { sendPort.send((id, RemoteError(e.message, ''))); } - case DownloadFile(:final file, :final path, :final downloadPath): - try { - final localFile = File('$downloadPath/${file.filename}'); - if (await localFile.exists()) { - sendPort.send((id, RemoteError('File Already Exists', ''))); - break; - } - final localFileWriter = await localFile.open(mode: FileMode.write); - final remoteFile = await sftpClient.open('$path${file.filename}'); - final fileSize = file.attr.size!; - bool timeout = true; - await for (final bytes in remoteFile.read( - onProgress: (progress) { - if (timeout) { - timeout = false; - sendPort.send((id, progress/fileSize)); - Future.delayed(Duration(seconds: 2), () => timeout = true); - } - } - )) { - await localFileWriter.writeFrom(bytes); - } - } - on SftpStatusError catch (e) { - sendPort.send((id, RemoteError(e.message, ''))); - } - sendPort.send((id, 1.0)); + case DownloadFile(): + downloadController.add((id, command)); } }); } From 7ea9e98789cd11a8651e6f3fd4a9a5fa836d6773 Mon Sep 17 00:00:00 2001 From: RafayAhmad7548 Date: Thu, 14 Aug 2025 07:45:59 +0500 Subject: [PATCH 3/4] remove test and remove todo from my diagnostics --- analysis_options.yaml | 3 +++ test/widget_test.dart | 30 ------------------------------ 2 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 test/widget_test.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index f9b3034..15369a7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,4 @@ include: package:flutter_lints/flutter.yaml +analyzer: + errors: + todo: ignore diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index dca0eae..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:fluxcloud/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} From a66ed70532c29080e00e516b4e0622e20dc2ab7b Mon Sep 17 00:00:00 2001 From: RafayAhmad7548 Date: Thu, 14 Aug 2025 15:45:41 +0500 Subject: [PATCH 4/4] move working --- lib/sftp_explorer.dart | 67 ++++++++++++++++++++++++++++++ lib/sftp_provider.dart | 12 ++++++ lib/sftp_worker.dart | 24 +++++++++++ lib/widgets/operation_buttons.dart | 6 ++- 4 files changed, 107 insertions(+), 2 deletions(-) diff --git a/lib/sftp_explorer.dart b/lib/sftp_explorer.dart index 7c4f4b6..3fdc9b8 100644 --- a/lib/sftp_explorer.dart +++ b/lib/sftp_explorer.dart @@ -1,10 +1,12 @@ import 'dart:io'; +import 'package:dartssh2/dartssh2.dart'; import 'package:file_picker/file_picker.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:fluxcloud/main.dart'; import 'package:fluxcloud/sftp_provider.dart'; +import 'package:path/path.dart'; import 'package:provider/provider.dart'; import 'widgets/operation_buttons.dart'; @@ -74,6 +76,7 @@ class SftpExplorer extends StatelessWidget { ], ), floatingActionButton: _buildFABs(context), + bottomNavigationBar: _buildCopyMoveButton(context), body: PopScope( canPop: false, onPopInvokedWithResult: (_, _) { @@ -203,4 +206,68 @@ class SftpExplorer extends StatelessWidget { ); } + Widget _buildCopyMoveButton(BuildContext context) { + return Selector?, bool)>( + selector: (_, sftpProvider) => (sftpProvider.toBeMovedOrCopied, sftpProvider.isCopy), + builder: (_, data, __) { + final (toBeMovedOrCopied, isCopy) = data; + if (toBeMovedOrCopied == null) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.all(12), + child: Row( + spacing: 10, + children: [ + Expanded(child: ElevatedButton( + onPressed: () async { + final sftpProvider = context.read(); + for (final filePath in toBeMovedOrCopied) { + try { + if (isCopy) { + + } + else { + final fileName = basename(filePath); + await sftpProvider.sftpWorker.rename(filePath, '${sftpProvider.path}$fileName'); + } + } + catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString())); + } + } + } + sftpProvider.setCopyOrMoveFiles(null, isCopy); + sftpProvider.listDir(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Text(isCopy ? 'Copy Here' : 'Move Here'), + ), + )), + IconButton( + onPressed: () { + context.read().setCopyOrMoveFiles(null, isCopy); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer + ), + icon: Padding( + padding: const EdgeInsets.all(4), + child: Icon(Icons.close), + ), + ) + ], + ), + ); + } + ); + } + } diff --git a/lib/sftp_provider.dart b/lib/sftp_provider.dart index 1059298..e3e12fd 100644 --- a/lib/sftp_provider.dart +++ b/lib/sftp_provider.dart @@ -11,6 +11,9 @@ class SftpProvider extends ChangeNotifier { double? _uploadProgress; double? _downloadProgress; + + List? _toBeMovedOrCopied; + bool _isCopy = false; SftpProvider(this._sftpWorker) { listDir(); @@ -24,6 +27,9 @@ class SftpProvider extends ChangeNotifier { double? get uploadProgress => _uploadProgress; double? get downloadProgress => _downloadProgress; + + List? get toBeMovedOrCopied => _toBeMovedOrCopied; + bool get isCopy => _isCopy; Future listDir() async { _isLoading = true; @@ -54,4 +60,10 @@ class SftpProvider extends ChangeNotifier { notifyListeners(); } + void setCopyOrMoveFiles(List? files, bool isCopy) { + _toBeMovedOrCopied = files; + _isCopy = isCopy; + notifyListeners(); + } + } diff --git a/lib/sftp_worker.dart b/lib/sftp_worker.dart index c6aaaae..87697bf 100644 --- a/lib/sftp_worker.dart +++ b/lib/sftp_worker.dart @@ -50,6 +50,13 @@ class DownloadFile extends SftpCommand { DownloadFile(this.file, this.path, this.downloadPath); } +class Copy extends SftpCommand { + final String filePath; + final String copyToPath; + + Copy(this.filePath, this.copyToPath); +} + class SftpWorker { @@ -227,6 +234,15 @@ class SftpWorker { } case DownloadFile(): downloadController.add((id, command)); + case Copy(:final filePath, :final copyToPath): + try { + // TODO: complete this + sendPort.send((id, 0)); + } + on SftpStatusError catch (e) { + sendPort.send((id, RemoteError(e.message, ''))); + } + } }); } @@ -309,4 +325,12 @@ class SftpWorker { return controller.stream; } + Future copy(String filePath, String copyToPath) async { + final completer = Completer.sync(); + final id = _idCounter++; + _activeRequests[id] = completer; + _commands.send((id, Copy(filePath, copyToPath))); + await completer.future; + } + } diff --git a/lib/widgets/operation_buttons.dart b/lib/widgets/operation_buttons.dart index 578657b..82bc312 100644 --- a/lib/widgets/operation_buttons.dart +++ b/lib/widgets/operation_buttons.dart @@ -20,13 +20,15 @@ class OperationButtons extends StatelessWidget { children: [ IconButton( onPressed: () { - + final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList(); + sftpProvider.setCopyOrMoveFiles(filePaths, false); }, icon: Icon(Icons.drive_file_move) ), IconButton( onPressed: () { - + final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList(); + sftpProvider.setCopyOrMoveFiles(filePaths, true); }, icon: Icon(Icons.copy) ),