select files feature and operations on them
This commit is contained in:
parent
7f3146d712
commit
99da431190
4 changed files with 85 additions and 30 deletions
|
@ -9,6 +9,8 @@ class SftpProvider extends ChangeNotifier {
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
late List<SftpName> _dirContents;
|
late List<SftpName> _dirContents;
|
||||||
|
|
||||||
|
final List<SftpName> _selectedFiles = [];
|
||||||
|
|
||||||
SftpProvider(this._sftpWorker) {
|
SftpProvider(this._sftpWorker) {
|
||||||
listDir();
|
listDir();
|
||||||
}
|
}
|
||||||
|
@ -18,6 +20,8 @@ class SftpProvider extends ChangeNotifier {
|
||||||
String get path => _path;
|
String get path => _path;
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
List<SftpName> get dirContents => _dirContents;
|
List<SftpName> get dirContents => _dirContents;
|
||||||
|
List<SftpName> get selectedFiles => _selectedFiles;
|
||||||
|
bool get isSelectionMode => _selectedFiles.isNotEmpty;
|
||||||
|
|
||||||
Future<void> listDir() async {
|
Future<void> listDir() async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
@ -38,4 +42,22 @@ class SftpProvider extends ChangeNotifier {
|
||||||
listDir();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:file_selector/file_selector.dart';
|
import 'package:file_selector/file_selector.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluxcloud/main.dart';
|
import 'package:fluxcloud/main.dart';
|
||||||
import 'package:fluxcloud/providers/sftp_loading_provider.dart';
|
import 'package:fluxcloud/providers/sftp_loading_provider.dart';
|
||||||
|
@ -34,7 +36,7 @@ class SftpExplorer extends StatelessWidget {
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.arrow_back)
|
icon: Icon(Icons.arrow_back)
|
||||||
),
|
),
|
||||||
actions: _buildLoadingButtons,
|
actions: [_buildAppBarButtons()]
|
||||||
),
|
),
|
||||||
floatingActionButton: _buildFABs(context),
|
floatingActionButton: _buildFABs(context),
|
||||||
bottomNavigationBar: _buildCopyMoveButton(context),
|
bottomNavigationBar: _buildCopyMoveButton(context),
|
||||||
|
@ -70,14 +72,28 @@ class SftpExplorer extends StatelessWidget {
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final dirEntry = sftpProvider.dirContents[index];
|
final dirEntry = sftpProvider.dirContents[index];
|
||||||
return ListTile(
|
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),
|
title: Text(dirEntry.filename),
|
||||||
trailing: OperationButtons(dirEntries: [dirEntry],),
|
trailing: sftpProvider.isSelectionMode ? null : OperationButtons(dirEntries: [dirEntry],),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (dirEntry.attr.isDirectory) {
|
if (sftpProvider.isSelectionMode) {
|
||||||
|
sftpProvider.toggleSelection(dirEntry);
|
||||||
|
}
|
||||||
|
else if (dirEntry.attr.isDirectory) {
|
||||||
sftpProvider.goToDir('${sftpProvider.path}${dirEntry.filename}/');
|
sftpProvider.goToDir('${sftpProvider.path}${dirEntry.filename}/');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
sftpProvider.selectFile(dirEntry);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -168,11 +184,11 @@ class SftpExplorer extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCopyMoveButton(BuildContext context) {
|
Widget _buildCopyMoveButton(BuildContext context) {
|
||||||
return Selector<SftpLoadingProvider, (List<String>?, bool)>(
|
return Selector<SftpLoadingProvider, (double?, List<String>?, bool)>(
|
||||||
selector: (_, sftpLoadingProvider) => (sftpLoadingProvider.toBeMovedOrCopied, sftpLoadingProvider.isCopy),
|
selector: (_, sftpLoadingProvider) => (sftpLoadingProvider.copyProgress, sftpLoadingProvider.toBeMovedOrCopied, sftpLoadingProvider.isCopy),
|
||||||
builder: (_, data, __) {
|
builder: (_, data, __) {
|
||||||
final (toBeMovedOrCopied, isCopy) = data;
|
final (copyProgress, toBeMovedOrCopied, isCopy) = data;
|
||||||
if (toBeMovedOrCopied == null) {
|
if (toBeMovedOrCopied == null || copyProgress != null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
|
@ -202,7 +218,6 @@ class SftpExplorer extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: figure out where to put this line
|
|
||||||
sftpLoadingProvider.setCopyOrMoveFiles(null, isCopy);
|
sftpLoadingProvider.setCopyOrMoveFiles(null, isCopy);
|
||||||
sftpLoadingProvider.setCopyProgress(null);
|
sftpLoadingProvider.setCopyProgress(null);
|
||||||
sftpProvider.listDir();
|
sftpProvider.listDir();
|
||||||
|
@ -236,20 +251,35 @@ class SftpExplorer extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> get _buildLoadingButtons => [
|
Widget _buildAppBarButtons() {
|
||||||
Selector<SftpLoadingProvider, double?>(
|
return Selector<SftpProvider, bool>(
|
||||||
selector: (_, sftpLoadingProvider) => sftpLoadingProvider.uploadProgress,
|
selector: (_, sftpProvider) => sftpProvider.isSelectionMode,
|
||||||
builder: (_, uploadProgress, __) => _buildLoader(uploadProgress, Icons.upload)
|
builder: (context, isSelectionMode, __) =>
|
||||||
),
|
isSelectionMode ?
|
||||||
Selector<SftpLoadingProvider, double?>(
|
Selector<SftpProvider, List<SftpName>>(
|
||||||
selector: (_, sftpLoadingProvider) => sftpLoadingProvider.downloadProgress,
|
selector: (_, sftpProvider) => sftpProvider.selectedFiles,
|
||||||
builder: (_, downloadProgress, __) => _buildLoader(downloadProgress, Icons.download)
|
shouldRebuild: (prev, next) => listEquals(prev, next),
|
||||||
),
|
builder: (_, selectedFiles, __) => OperationButtons(dirEntries: [...selectedFiles])
|
||||||
Selector<SftpLoadingProvider, double?>(
|
)
|
||||||
selector: (_, sftpLoadingProvider) => sftpLoadingProvider.copyProgress,
|
: Row(
|
||||||
builder: (_, copyProgress, __) => _buildLoader(copyProgress, Icons.copy)
|
mainAxisSize: MainAxisSize.min,
|
||||||
),
|
children: [
|
||||||
];
|
Selector<SftpLoadingProvider, double?>(
|
||||||
|
selector: (_, sftpLoadingProvider) => sftpLoadingProvider.uploadProgress,
|
||||||
|
builder: (_, uploadProgress, __) => _buildLoader(uploadProgress, Icons.upload)
|
||||||
|
),
|
||||||
|
Selector<SftpLoadingProvider, double?>(
|
||||||
|
selector: (_, sftpLoadingProvider) => sftpLoadingProvider.downloadProgress,
|
||||||
|
builder: (_, downloadProgress, __) => _buildLoader(downloadProgress, Icons.download)
|
||||||
|
),
|
||||||
|
Selector<SftpLoadingProvider, double?>(
|
||||||
|
selector: (_, sftpLoadingProvider) => sftpLoadingProvider.copyProgress,
|
||||||
|
builder: (_, copyProgress, __) => _buildLoader(copyProgress, Icons.copy)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildLoader(double? progress, IconData icon) {
|
Widget _buildLoader(double? progress, IconData icon) {
|
||||||
return progress != null ? Stack(
|
return progress != null ? Stack(
|
||||||
|
|
|
@ -185,8 +185,7 @@ class SftpWorker {
|
||||||
final dstFile = await sftpClient.open(copyCmd.copyToPath, mode: SftpFileOpenMode.create | SftpFileOpenMode.write | SftpFileOpenMode.exclusive);
|
final dstFile = await sftpClient.open(copyCmd.copyToPath, mode: SftpFileOpenMode.create | SftpFileOpenMode.write | SftpFileOpenMode.exclusive);
|
||||||
final fileSize = (await srcFile.stat()).size!;
|
final fileSize = (await srcFile.stat()).size!;
|
||||||
bool timeout = true;
|
bool timeout = true;
|
||||||
await dstFile.write(
|
await dstFile.write(srcFile.read(
|
||||||
srcFile.read(),
|
|
||||||
onProgress: (progress) {
|
onProgress: (progress) {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
timeout = false;
|
timeout = false;
|
||||||
|
@ -194,7 +193,7 @@ class SftpWorker {
|
||||||
Future.delayed(Duration(seconds: 2), () => timeout = true);
|
Future.delayed(Duration(seconds: 2), () => timeout = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
on SftpStatusError catch (e) {
|
on SftpStatusError catch (e) {
|
||||||
sendPort.send((id, RemoteError(e.message, '')));
|
sendPort.send((id, RemoteError(e.message, '')));
|
||||||
|
|
|
@ -22,6 +22,7 @@ class OperationButtons extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
sftpProvider.clearSelectedFiles();
|
||||||
final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList();
|
final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList();
|
||||||
sftpLoadingProvider.setCopyOrMoveFiles(filePaths, false);
|
sftpLoadingProvider.setCopyOrMoveFiles(filePaths, false);
|
||||||
},
|
},
|
||||||
|
@ -29,6 +30,7 @@ class OperationButtons extends StatelessWidget {
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
sftpProvider.clearSelectedFiles();
|
||||||
final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList();
|
final filePaths = dirEntries.map((dirEntry) => '${sftpProvider.path}${dirEntry.filename}').toList();
|
||||||
sftpLoadingProvider.setCopyOrMoveFiles(filePaths, true);
|
sftpLoadingProvider.setCopyOrMoveFiles(filePaths, true);
|
||||||
},
|
},
|
||||||
|
@ -105,11 +107,12 @@ class OperationButtons extends StatelessWidget {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString()));
|
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')
|
child: Text('Yes')
|
||||||
),
|
),
|
||||||
|
@ -122,6 +125,7 @@ class OperationButtons extends StatelessWidget {
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
sftpProvider.clearSelectedFiles();
|
||||||
final downloadsDir = await getDownloadsDirectory();
|
final downloadsDir = await getDownloadsDirectory();
|
||||||
if (downloadsDir == null) return;
|
if (downloadsDir == null) return;
|
||||||
for (final dirEntry in dirEntries) {
|
for (final dirEntry in dirEntries) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue