download worky, ui bit buggy with mutiple downlaods

This commit is contained in:
RafayAhmad7548 2025-08-13 09:58:47 +05:00
parent 3f12b14e8a
commit 33ade0479f
5 changed files with 114 additions and 15 deletions

View file

@ -25,7 +25,10 @@ class _SftpExplorerState extends State<SftpExplorer> {
bool _isLoading = true; bool _isLoading = true;
late List<SftpName> _dirContents; late List<SftpName> _dirContents;
double? _progress; double? _uploadProgress;
double? _downloadProgress;
void _setDownloadProgress(double? progress) => setState(() => _downloadProgress = progress);
@override @override
void initState() { void initState() {
@ -70,12 +73,29 @@ class _SftpExplorerState extends State<SftpExplorer> {
icon: Icon(Icons.arrow_back) icon: Icon(Icons.arrow_back)
), ),
actions: [ actions: [
if (_progress != null) if (_uploadProgress != null)
Stack( Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _progress), 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)
),
]
),
if (_downloadProgress != null)
Stack(
alignment: Alignment.center,
children: [
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _downloadProgress),
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,) builder: (context, value, _) => CircularProgressIndicator(strokeWidth: 3, value: value,)
), ),
@ -83,7 +103,7 @@ class _SftpExplorerState extends State<SftpExplorer> {
onPressed: () { onPressed: () {
// TODO: show donwload details here // TODO: show donwload details here
}, },
icon: Icon(Icons.upload) icon: Icon(Icons.download)
), ),
] ]
), ),
@ -125,7 +145,7 @@ class _SftpExplorerState extends State<SftpExplorer> {
return ListTile( return ListTile(
leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description), leading: Icon(dirEntry.attr.isDirectory ? Icons.folder : Icons.description),
title: Text(dirEntry.filename), title: Text(dirEntry.filename),
trailing: OperationButtons(sftpWorker: widget.sftpWorker, path: path, dirEntries: [dirEntry], listDir: _listDir,), trailing: OperationButtons(sftpWorker: widget.sftpWorker, path: path, dirEntries: [dirEntry], listDir: _listDir, setDownloadProgress: _setDownloadProgress,),
onTap: () { onTap: () {
if (dirEntry.attr.isDirectory) { if (dirEntry.attr.isDirectory) {
path = '$path${dirEntry.filename}/'; path = '$path${dirEntry.filename}/';
@ -197,8 +217,17 @@ class _SftpExplorerState extends State<SftpExplorer> {
filePaths = files.map((file) => file.path).toList(); filePaths = files.map((file) => file.path).toList();
} }
try { try {
bool start = true;
await for (final progress in widget.sftpWorker.uploadFiles(path, filePaths)) { await for (final progress in widget.sftpWorker.uploadFiles(path, filePaths)) {
setState(() => _progress = progress); if (start) {
start = false;
_listDir();
}
setState(() => _uploadProgress = progress);
if (progress == 1) {
// TODO: fix: next file also starts to show
_listDir();
}
} }
} }
catch (e) { catch (e) {
@ -206,7 +235,7 @@ class _SftpExplorerState extends State<SftpExplorer> {
ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString())); ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString()));
} }
} }
setState(() => _progress = null); setState(() => _uploadProgress = null);
_listDir(); _listDir();
}, },
child: Icon(Icons.upload), child: Icon(Icons.upload),

View file

@ -17,9 +17,9 @@ class ListDir extends SftpCommand {
class UploadFiles extends SftpCommand { class UploadFiles extends SftpCommand {
final String path; final String path;
final List<String> fileNames; final List<String> filePaths;
UploadFiles(this.path, this.fileNames); UploadFiles(this.path, this.filePaths);
} }
class MkDir extends SftpCommand { class MkDir extends SftpCommand {
@ -42,6 +42,14 @@ class Rename extends SftpCommand {
Rename(this.oldpath, this.newpath); Rename(this.oldpath, this.newpath);
} }
class DownloadFiles extends SftpCommand {
final List<SftpName> files;
final String path;
final String downloadPath;
DownloadFiles(this.files, this.path, this.downloadPath);
}
class SftpWorker { class SftpWorker {
@ -112,7 +120,7 @@ class SftpWorker {
on SftpStatusError catch (e) { on SftpStatusError catch (e) {
sendPort.send((id, RemoteError(e.message, ''))); sendPort.send((id, RemoteError(e.message, '')));
} }
case UploadFiles(:final path, fileNames:final filePaths): case UploadFiles(:final path, :final filePaths):
for (var filePath in filePaths) { for (var filePath in filePaths) {
try { try {
final file = File(filePath); final file = File(filePath);
@ -138,6 +146,7 @@ class SftpWorker {
} }
sendPort.send((id, 1.0)); sendPort.send((id, 1.0));
} }
sendPort.send((id, null));
case MkDir(:final path): case MkDir(:final path):
try { try {
await sftpClient.mkdir(path); await sftpClient.mkdir(path);
@ -181,12 +190,42 @@ class SftpWorker {
on SftpStatusError catch (e) { on SftpStatusError catch (e) {
sendPort.send((id, RemoteError(e.message, ''))); sendPort.send((id, RemoteError(e.message, '')));
} }
case DownloadFiles(:final files, :final path, :final downloadPath):
for (final file in files) {
try {
final localFile = File('$downloadPath/${file.filename}');
if (await localFile.exists()) {
sendPort.send((id, RemoteError('File Already Exists', '')));
continue;
}
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));
}
sendPort.send((id, null));
} }
}); });
} }
void _sftpResponseHandler(dynamic message) { void _sftpResponseHandler(dynamic message) {
final (int id, Object response) = message; final (int id, Object? response) = message;
if (_activeRequests[id] is Completer) { if (_activeRequests[id] is Completer) {
final completer = _activeRequests.remove(id)!; final completer = _activeRequests.remove(id)!;
@ -204,11 +243,13 @@ class SftpWorker {
controller.addError(response); controller.addError(response);
} }
else { else {
controller.add(response); if (response == null) {
if (response == 1) {
controller.close(); controller.close();
_activeRequests.remove(id); _activeRequests.remove(id);
} }
else {
controller.add(response);
}
} }
} }
} }
@ -255,4 +296,12 @@ class SftpWorker {
await completer.future; await completer.future;
} }
Stream<double> downloadFiles(List<SftpName> files, String path, String downloadPath) {
final controller = StreamController<double>();
final id = _idCounter++;
_activeRequests[id] = controller;
_commands.send((id, DownloadFiles(files, path, downloadPath)));
return controller.stream;
}
} }

View file

@ -2,17 +2,19 @@ import 'package:dartssh2/dartssh2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluxcloud/main.dart'; import 'package:fluxcloud/main.dart';
import 'package:fluxcloud/sftp_worker.dart'; import 'package:fluxcloud/sftp_worker.dart';
import 'package:path_provider/path_provider.dart';
class OperationButtons extends StatelessWidget { class OperationButtons extends StatelessWidget {
const OperationButtons({ const OperationButtons({
super.key, super.key,
required this.sftpWorker, required this.path, required this.dirEntries, required this.listDir, required this.sftpWorker, required this.path, required this.dirEntries, required this.listDir, required this.setDownloadProgress,
}); });
final SftpWorker sftpWorker; final SftpWorker sftpWorker;
final String path; final String path;
final List<SftpName> dirEntries; final List<SftpName> dirEntries;
final Function listDir; final Function listDir;
final Function(double? progress) setDownloadProgress;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -117,6 +119,24 @@ class OperationButtons extends StatelessWidget {
}, },
icon: Icon(Icons.delete) icon: Icon(Icons.delete)
), ),
IconButton(
onPressed: () async {
final downloadsDir = await getDownloadsDirectory();
if (downloadsDir == null) return;
try {
await for (final progress in sftpWorker.downloadFiles(dirEntries, path, downloadsDir.path)) {
setDownloadProgress(progress);
}
setDownloadProgress(null);
}
catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(buildErrorSnackBar(context, e.toString()));
}
}
},
icon: Icon(Icons.download)
)
], ],
); );
} }

View file

@ -329,7 +329,7 @@ packages:
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_provider: path_provider:
dependency: transitive dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"

View file

@ -14,6 +14,7 @@ dependencies:
dartssh2: ^2.13.0 dartssh2: ^2.13.0
path: ^1.9.1 path: ^1.9.1
file_picker: ^10.2.0 file_picker: ^10.2.0
path_provider: ^2.1.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: