diff --git a/lib/providers/sftp_provider.dart b/lib/providers/sftp_provider.dart index c4cc988..14fea48 100644 --- a/lib/providers/sftp_provider.dart +++ b/lib/providers/sftp_provider.dart @@ -9,6 +9,8 @@ class SftpProvider extends ChangeNotifier { bool _isLoading = false; late List _dirContents; + final List _selectedFiles = []; + SftpProvider(this._sftpWorker) { listDir(); } @@ -18,6 +20,8 @@ class SftpProvider extends ChangeNotifier { String get path => _path; bool get isLoading => _isLoading; List get dirContents => _dirContents; + List get selectedFiles => _selectedFiles; + bool get isSelectionMode => _selectedFiles.isNotEmpty; Future listDir() async { _isLoading = true; @@ -38,4 +42,22 @@ class SftpProvider extends ChangeNotifier { listDir(); } + void selectFile(SftpName file) { + _selectedFiles.add(file); + notifyListeners(); + } + + void toggleSelection(SftpName file) { + if (!_selectedFiles.remove(file)) { + _selectedFiles.add(file); + } + notifyListeners(); + } + + void clearSelectedFiles() { + _selectedFiles.clear(); + notifyListeners(); + } + + } diff --git a/lib/sftp_explorer.dart b/lib/sftp_explorer.dart index 2157d06..57ce770 100644 --- a/lib/sftp_explorer.dart +++ b/lib/sftp_explorer.dart @@ -1,7 +1,9 @@ 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/foundation.dart'; import 'package:flutter/material.dart'; import 'package:fluxcloud/main.dart'; import 'package:fluxcloud/providers/sftp_loading_provider.dart'; @@ -34,7 +36,7 @@ class SftpExplorer extends StatelessWidget { }, icon: Icon(Icons.arrow_back) ), - actions: _buildLoadingButtons, + actions: [_buildAppBarButtons()] ), floatingActionButton: _buildFABs(context), bottomNavigationBar: _buildCopyMoveButton(context), @@ -70,14 +72,28 @@ class SftpExplorer extends StatelessWidget { itemBuilder: (context, index) { final dirEntry = sftpProvider.dirContents[index]; return ListTile( - leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description), + leading: Row( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + if (sftpProvider.isSelectionMode) + Icon(sftpProvider.selectedFiles.contains(dirEntry) ? Icons.check_box : Icons.check_box_outline_blank), + Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description), + ], + ), title: Text(dirEntry.filename), - trailing: OperationButtons(dirEntries: [dirEntry],), + trailing: sftpProvider.isSelectionMode ? null : OperationButtons(dirEntries: [dirEntry],), onTap: () { - if (dirEntry.attr.isDirectory) { + if (sftpProvider.isSelectionMode) { + sftpProvider.toggleSelection(dirEntry); + } + else if (dirEntry.attr.isDirectory) { sftpProvider.goToDir('${sftpProvider.path}${dirEntry.filename}/'); } }, + onLongPress: () { + sftpProvider.selectFile(dirEntry); + }, ); }, ) @@ -168,11 +184,11 @@ class SftpExplorer extends StatelessWidget { } Widget _buildCopyMoveButton(BuildContext context) { - return Selector?, bool)>( - selector: (_, sftpLoadingProvider) => (sftpLoadingProvider.toBeMovedOrCopied, sftpLoadingProvider.isCopy), + return Selector?, bool)>( + selector: (_, sftpLoadingProvider) => (sftpLoadingProvider.copyProgress, sftpLoadingProvider.toBeMovedOrCopied, sftpLoadingProvider.isCopy), builder: (_, data, __) { - final (toBeMovedOrCopied, isCopy) = data; - if (toBeMovedOrCopied == null) { + final (copyProgress, toBeMovedOrCopied, isCopy) = data; + if (toBeMovedOrCopied == null || copyProgress != null) { return const SizedBox.shrink(); } return Padding( @@ -202,7 +218,6 @@ class SftpExplorer extends StatelessWidget { } } } - // TODO: figure out where to put this line sftpLoadingProvider.setCopyOrMoveFiles(null, isCopy); sftpLoadingProvider.setCopyProgress(null); sftpProvider.listDir(); @@ -236,20 +251,35 @@ class SftpExplorer extends StatelessWidget { ); } - List get _buildLoadingButtons => [ - Selector( - selector: (_, sftpLoadingProvider) => sftpLoadingProvider.uploadProgress, - builder: (_, uploadProgress, __) => _buildLoader(uploadProgress, Icons.upload) - ), - Selector( - selector: (_, sftpLoadingProvider) => sftpLoadingProvider.downloadProgress, - builder: (_, downloadProgress, __) => _buildLoader(downloadProgress, Icons.download) - ), - Selector( - selector: (_, sftpLoadingProvider) => sftpLoadingProvider.copyProgress, - builder: (_, copyProgress, __) => _buildLoader(copyProgress, Icons.copy) - ), - ]; + Widget _buildAppBarButtons() { + return Selector( + selector: (_, sftpProvider) => sftpProvider.isSelectionMode, + builder: (context, isSelectionMode, __) => + isSelectionMode ? + Selector>( + selector: (_, sftpProvider) => sftpProvider.selectedFiles, + shouldRebuild: (prev, next) => listEquals(prev, next), + builder: (_, selectedFiles, __) => OperationButtons(dirEntries: [...selectedFiles]) + ) + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + Selector( + selector: (_, sftpLoadingProvider) => sftpLoadingProvider.uploadProgress, + builder: (_, uploadProgress, __) => _buildLoader(uploadProgress, Icons.upload) + ), + Selector( + selector: (_, sftpLoadingProvider) => sftpLoadingProvider.downloadProgress, + builder: (_, downloadProgress, __) => _buildLoader(downloadProgress, Icons.download) + ), + Selector( + selector: (_, sftpLoadingProvider) => sftpLoadingProvider.copyProgress, + builder: (_, copyProgress, __) => _buildLoader(copyProgress, Icons.copy) + ), + ], + ), + ); + } Widget _buildLoader(double? progress, IconData icon) { return progress != null ? Stack( diff --git a/lib/sftp_worker.dart b/lib/sftp_worker.dart index 72c1901..30aff5c 100644 --- a/lib/sftp_worker.dart +++ b/lib/sftp_worker.dart @@ -185,8 +185,7 @@ class SftpWorker { final dstFile = await sftpClient.open(copyCmd.copyToPath, mode: SftpFileOpenMode.create | SftpFileOpenMode.write | SftpFileOpenMode.exclusive); final fileSize = (await srcFile.stat()).size!; bool timeout = true; - await dstFile.write( - srcFile.read(), + await dstFile.write(srcFile.read( onProgress: (progress) { if (timeout) { timeout = false; @@ -194,7 +193,7 @@ class SftpWorker { Future.delayed(Duration(seconds: 2), () => timeout = true); } } - ); + )); } on SftpStatusError catch (e) { sendPort.send((id, RemoteError(e.message, ''))); diff --git a/lib/widgets/operation_buttons.dart b/lib/widgets/operation_buttons.dart index af0ed26..ca9411b 100644 --- a/lib/widgets/operation_buttons.dart +++ b/lib/widgets/operation_buttons.dart @@ -22,6 +22,7 @@ class OperationButtons extends StatelessWidget { children: [ IconButton( onPressed: () { + sftpProvider.clearSelectedFiles(); final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList(); sftpLoadingProvider.setCopyOrMoveFiles(filePaths, false); }, @@ -29,6 +30,7 @@ class OperationButtons extends StatelessWidget { ), IconButton( onPressed: () { + sftpProvider.clearSelectedFiles(); final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList(); sftpLoadingProvider.setCopyOrMoveFiles(filePaths, true); }, @@ -105,11 +107,12 @@ class OperationButtons extends StatelessWidget { ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString())); } } - sftpProvider.listDir(); - if (context.mounted) { - Navigator.pop(context); - } } + sftpProvider.listDir(); + if (context.mounted) { + Navigator.pop(context); + } + sftpProvider.clearSelectedFiles(); }, child: Text('Yes') ), @@ -122,6 +125,7 @@ class OperationButtons extends StatelessWidget { ), IconButton( onPressed: () async { + sftpProvider.clearSelectedFiles(); final downloadsDir = await getDownloadsDirectory(); if (downloadsDir == null) return; for (final dirEntry in dirEntries) {