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: