Change SFTP to single column

This commit is contained in:
Junyuan Feng
2022-03-08 18:00:46 +08:00
parent 241002c3ea
commit 7a5516792c
2 changed files with 62 additions and 108 deletions

View File

@@ -3,43 +3,12 @@ import 'package:toolbox/data/model/server/server_private_info.dart';
import 'package:toolbox/data/model/sftp/absolute_path.dart'; import 'package:toolbox/data/model/sftp/absolute_path.dart';
class SFTPSideViewStatus { class SFTPSideViewStatus {
bool leftSelected = false; bool selected = false;
bool rightSelected = false; ServerPrivateInfo? spi;
ServerPrivateInfo? leftSpi; List<SftpName>? files;
ServerPrivateInfo? rightSpi; AbsolutePath? path;
List<SftpName>? leftFiles; SftpClient? client;
List<SftpName>? rightFiles; bool isBusy = false;
AbsolutePath? leftPath;
AbsolutePath? rightPath;
SftpClient? leftClient;
SftpClient? rightClient;
bool isBusyLeft = false;
bool isBusyRight = false;
SFTPSideViewStatus(); SFTPSideViewStatus();
ServerPrivateInfo? spi(bool left) => left ? leftSpi : rightSpi;
void setSpi(bool left, ServerPrivateInfo nSpi) =>
left ? leftSpi = nSpi : rightSpi = nSpi;
/// Whether the Left/Right Destination is selected.
bool selected(bool left) => left ? leftSelected : rightSelected;
void setSelect(bool left, bool nSelect) =>
left ? leftSelected = nSelect : rightSelected = nSelect;
List<SftpName>? files(bool left) => left ? leftFiles : rightFiles;
void setFiles(bool left, List<SftpName>? nFiles) =>
left ? leftFiles = nFiles : rightFiles = nFiles;
AbsolutePath? path(bool left) => left ? leftPath : rightPath;
void setPath(bool left, AbsolutePath? nPath) =>
left ? leftPath = nPath : rightPath = nPath;
SftpClient? client(bool left) => left ? leftClient : rightClient;
void setClient(bool left, SftpClient? nClient) =>
left ? leftClient = nClient : rightClient = nClient;
bool isBusy(bool left) => left ? isBusyLeft : isBusyRight;
void setBusy(bool left, bool nBusy) =>
left ? isBusyLeft = nBusy : isBusyRight = nBusy;
} }

View File

@@ -8,6 +8,7 @@ import 'package:toolbox/data/model/sftp/sftp_side_status.dart';
import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/provider/server.dart';
import 'package:toolbox/locator.dart'; import 'package:toolbox/locator.dart';
import 'package:toolbox/view/widget/fade_in.dart'; import 'package:toolbox/view/widget/fade_in.dart';
import 'package:toolbox/view/widget/two_line_text.dart';
class SFTPPage extends StatefulWidget { class SFTPPage extends StatefulWidget {
final ServerPrivateInfo? spi; final ServerPrivateInfo? spi;
@@ -20,8 +21,7 @@ class SFTPPage extends StatefulWidget {
class _SFTPPageState extends State<SFTPPage> { class _SFTPPageState extends State<SFTPPage> {
final SFTPSideViewStatus _status = SFTPSideViewStatus(); final SFTPSideViewStatus _status = SFTPSideViewStatus();
final ScrollController _leftScrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final ScrollController _rightScrollController = ScrollController();
late MediaQueryData _media; late MediaQueryData _media;
@@ -35,8 +35,8 @@ class _SFTPPageState extends State<SFTPPage> {
void initState() { void initState() {
super.initState(); super.initState();
if (widget.spi != null) { if (widget.spi != null) {
_status.setSpi(true, widget.spi!); _status.spi = widget.spi!;
_status.setSelect(true, true); _status.selected = true;
} }
} }
@@ -45,24 +45,9 @@ class _SFTPPageState extends State<SFTPPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: const Text('SFTP'), title: TwoLineText(up: 'SFTP', down: _status.spi?.ip ?? '...'),
), ),
body: Row( body: _buildFileView(),
children: [
_buildSingleColumn(true),
const VerticalDivider(
width: 2,
),
_buildSingleColumn(false),
],
),
);
}
Widget _buildSingleColumn(bool left) {
return SizedBox(
width: (_media.size.width - 2) / 2,
child: _buildFileView(left),
); );
} }
@@ -77,15 +62,15 @@ class _SFTPPageState extends State<SFTPPage> {
), ),
); );
Widget _buildFileView(bool left) { Widget _buildFileView() {
if (!_status.selected(left)) { if (!_status.selected) {
return ListView( return ListView(
children: [ children: [
_buildDestSelector(left), _buildDestSelector(),
], ],
); );
} }
final spi = _status.spi(left); final spi = _status.spi;
final si = final si =
locator<ServerProvider>().servers.firstWhere((s) => s.info == spi); locator<ServerProvider>().servers.firstWhere((s) => s.info == spi);
final client = si.client; final client = si.client;
@@ -94,21 +79,21 @@ class _SFTPPageState extends State<SFTPPage> {
return centerCircleLoading; return centerCircleLoading;
} }
if (_status.files(left) == null) { if (_status.files == null) {
_status.setPath(left, AbsolutePath('/')); _status.path = AbsolutePath('/');
listDir(left, path: '/', client: client); listDir(path: '/', client: client);
return centerCircleLoading; return centerCircleLoading;
} else { } else {
return RefreshIndicator( return RefreshIndicator(
child: FadeIn( child: FadeIn(
child: ListView.builder( child: ListView.builder(
itemCount: _status.files(left)!.length + 1, itemCount: _status.files!.length + 1,
controller: left ? _leftScrollController : _rightScrollController, controller: _scrollController,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == 0) { if (index == 0) {
return _buildDestSelector(left); return _buildDestSelector();
} }
final file = _status.files(left)![index - 1]; final file = _status.files![index - 1];
final isDir = file.attr.mode?.isDirectory ?? true; final isDir = file.attr.mode?.isDirectory ?? true;
return ListTile( return ListTile(
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file), leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
@@ -117,23 +102,23 @@ class _SFTPPageState extends State<SFTPPage> {
isDir ? null : Text(convertBytes(file.attr.size ?? 0)), isDir ? null : Text(convertBytes(file.attr.size ?? 0)),
onTap: () { onTap: () {
if (isDir) { if (isDir) {
_status.path(left)?.update(file.filename); _status.path?.update(file.filename);
listDir(left, path: _status.path(left)?.path); listDir(path: _status.path?.path);
} else { } else {
onItemPress(context, left, file); onItemPress(context, file);
} }
}, },
onLongPress: () => onItemPress(context, left, file), onLongPress: () => onItemPress(context, file),
); );
}, },
), ),
key: Key(_status.spi(left)!.name + _status.path(left)!.path), key: Key(_status.spi!.name + _status.path!.path),
), ),
onRefresh: () => listDir(left, path: _status.path(left)?.path)); onRefresh: () => listDir(path: _status.path?.path));
} }
} }
void onItemPress(BuildContext context, bool left, SftpName file) { void onItemPress(BuildContext context, SftpName file) {
showRoundDialog( showRoundDialog(
context, context,
'Action', 'Action',
@@ -143,16 +128,16 @@ class _SFTPPageState extends State<SFTPPage> {
ListTile( ListTile(
leading: const Icon(Icons.delete), leading: const Icon(Icons.delete),
title: const Text('Delete'), title: const Text('Delete'),
onTap: () => delete(context, left, file), onTap: () => delete(context, file),
), ),
ListTile( ListTile(
leading: const Icon(Icons.folder), leading: const Icon(Icons.folder),
title: const Text('Create Folder'), title: const Text('Create Folder'),
onTap: () => mkdir(context, left)), onTap: () => mkdir(context)),
ListTile( ListTile(
leading: const Icon(Icons.edit), leading: const Icon(Icons.edit),
title: const Text('Rename'), title: const Text('Rename'),
onTap: () => rename(context, left, file), onTap: () => rename(context, file),
), ),
], ],
), ),
@@ -163,7 +148,7 @@ class _SFTPPageState extends State<SFTPPage> {
]); ]);
} }
void delete(BuildContext context, bool left, SftpName file) { void delete(BuildContext context, SftpName file) {
Navigator.of(context).pop(); Navigator.of(context).pop();
showRoundDialog( showRoundDialog(
context, 'Confirm', Text('Are you sure to delete ${file.filename}?'), [ context, 'Confirm', Text('Are you sure to delete ${file.filename}?'), [
@@ -172,9 +157,9 @@ class _SFTPPageState extends State<SFTPPage> {
child: const Text('Cancel')), child: const Text('Cancel')),
TextButton( TextButton(
onPressed: () { onPressed: () {
_status.client(left)!.remove(file.filename); _status.client!.remove(file.filename);
Navigator.of(context).pop(); Navigator.of(context).pop();
listDir(left); listDir();
}, },
child: const Text( child: const Text(
'Delete', 'Delete',
@@ -183,7 +168,7 @@ class _SFTPPageState extends State<SFTPPage> {
]); ]);
} }
void mkdir(BuildContext context, bool left) { void mkdir(BuildContext context) {
Navigator.of(context).pop(); Navigator.of(context).pop();
final textController = TextEditingController(); final textController = TextEditingController();
showRoundDialog( showRoundDialog(
@@ -210,10 +195,10 @@ class _SFTPPageState extends State<SFTPPage> {
]); ]);
return; return;
} }
_status.client(left)!.mkdir( _status.client!.mkdir(
_status.path(left)!.path + '/' + textController.text); _status.path!.path + '/' + textController.text);
Navigator.of(context).pop(); Navigator.of(context).pop();
listDir(left); listDir();
}, },
child: const Text( child: const Text(
'Create', 'Create',
@@ -222,7 +207,7 @@ class _SFTPPageState extends State<SFTPPage> {
]); ]);
} }
void rename(BuildContext context, bool left, SftpName file) { void rename(BuildContext context, SftpName file) {
Navigator.of(context).pop(); Navigator.of(context).pop();
final textController = TextEditingController(); final textController = TextEditingController();
showRoundDialog( showRoundDialog(
@@ -250,10 +235,10 @@ class _SFTPPageState extends State<SFTPPage> {
return; return;
} }
await _status await _status
.client(left)! .client!
.rename(file.filename, textController.text); .rename(file.filename, textController.text);
Navigator.of(context).pop(); Navigator.of(context).pop();
listDir(left); listDir();
}, },
child: const Text( child: const Text(
'Rename', 'Rename',
@@ -276,33 +261,33 @@ class _SFTPPageState extends State<SFTPPage> {
return '$finalValue ${suffix[squareTimes]}'; return '$finalValue ${suffix[squareTimes]}';
} }
Future<void> listDir(bool left, {String? path, SSHClient? client}) async { Future<void> listDir({String? path, SSHClient? client}) async {
if (_status.isBusy(left)) { if (_status.isBusy) {
return; return;
} }
_status.setBusy(left, true); _status.isBusy = true;
if (client != null) { if (client != null) {
final sftpc = await client.sftp(); final sftpc = await client.sftp();
_status.setClient(left, sftpc); _status.client = sftpc;
} }
final fs = await _status final fs = await _status
.client(left)! .client!
.listdir(path ?? (_status.leftPath?.path ?? '/')); .listdir(path ?? (_status.path?.path ?? '/'));
fs.sort((a, b) => a.filename.compareTo(b.filename)); fs.sort((a, b) => a.filename.compareTo(b.filename));
fs.removeAt(0); fs.removeAt(0);
if (mounted) { if (mounted) {
setState(() { setState(() {
_status.setFiles(left, fs); _status.files = fs;
_status.setBusy(left, false); _status.isBusy = false;
}); });
} }
} }
Widget _buildDestSelector(bool left) { Widget _buildDestSelector() {
final str = _status.path(left)?.path; final str = _status.path?.path;
return ExpansionTile( return ExpansionTile(
title: Text(_status.spi(left)?.name ?? 'Choose target'), title: Text(_status.spi?.name ?? 'Choose target'),
subtitle: _status.selected(left) subtitle: _status.selected
? LayoutBuilder(builder: (context, size) { ? LayoutBuilder(builder: (context, size) {
bool exceeded = false; bool exceeded = false;
int len = 0; int len = 0;
@@ -341,20 +326,20 @@ class _SFTPPageState extends State<SFTPPage> {
: null, : null,
children: locator<ServerProvider>() children: locator<ServerProvider>()
.servers .servers
.map((e) => _buildDestSelectorItem(e.info, left)) .map((e) => _buildDestSelectorItem(e.info))
.toList()); .toList());
} }
Widget _buildDestSelectorItem(ServerPrivateInfo spi, bool left) { Widget _buildDestSelectorItem(ServerPrivateInfo spi) {
return ListTile( return ListTile(
title: Text(spi.name), title: Text(spi.name),
subtitle: Text('${spi.user}@${spi.ip}:${spi.port}'), subtitle: Text('${spi.user}@${spi.ip}:${spi.port}'),
onTap: () { onTap: () {
_status.setSpi(left, spi); _status.spi = spi;
_status.setSelect(left, true); _status.selected = true;
_status.setPath(left, AbsolutePath('/')); _status.path = AbsolutePath('/');
listDir(left, listDir(
client: locator<ServerProvider>() client: locator<ServerProvider>()
.servers .servers
.firstWhere((s) => s.info == spi) .firstWhere((s) => s.info == spi)
.client, .client,