mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
50% sftp
This commit is contained in:
@@ -354,7 +354,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 97;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.96;
|
MARKETING_VERSION = 1.0.97;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -484,7 +484,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 97;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -492,7 +492,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.96;
|
MARKETING_VERSION = 1.0.97;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -508,7 +508,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 97;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -516,7 +516,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.96;
|
MARKETING_VERSION = 1.0.97;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
|||||||
@@ -191,8 +191,9 @@ class ServerProvider extends BusyProvider {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
_servers[idx].connectionState = ServerConnectionState.failed;
|
_servers[idx].connectionState = ServerConnectionState.failed;
|
||||||
servers[idx].status.failedInfo = e.toString();
|
servers[idx].status.failedInfo = e.toString();
|
||||||
notifyListeners();
|
|
||||||
logger.warning(e);
|
logger.warning(e);
|
||||||
|
} finally {
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +220,6 @@ class ServerProvider extends BusyProvider {
|
|||||||
results.add(NetSpeedPart(device, bytesIn, bytesOut, time));
|
results.add(NetSpeedPart(device, bytesIn, bytesOut, time));
|
||||||
}
|
}
|
||||||
info.status.netSpeed = info.status.netSpeed.update(results);
|
info.status.netSpeed = info.status.netSpeed.update(results);
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getSysVer(ServerPrivateInfo spi, String raw) {
|
void _getSysVer(ServerPrivateInfo spi, String raw) {
|
||||||
@@ -228,8 +228,6 @@ class ServerProvider extends BusyProvider {
|
|||||||
if (s.length == 2) {
|
if (s.length == 2) {
|
||||||
info.status.sysVer = s[1].replaceAll('"', '').replaceFirst('\n', '');
|
info.status.sysVer = s[1].replaceAll('"', '').replaceFirst('\n', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getCPUTemp(String raw) {
|
String _getCPUTemp(String raw) {
|
||||||
@@ -264,14 +262,11 @@ class ServerProvider extends BusyProvider {
|
|||||||
info.status.cpu2Status =
|
info.status.cpu2Status =
|
||||||
info.status.cpu2Status.update(cpus, _getCPUTemp(temp));
|
info.status.cpu2Status.update(cpus, _getCPUTemp(temp));
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getUpTime(ServerPrivateInfo spi, String raw) {
|
void _getUpTime(ServerPrivateInfo spi, String raw) {
|
||||||
_servers.firstWhere((e) => e.info == spi).status.uptime =
|
_servers.firstWhere((e) => e.info == spi).status.uptime =
|
||||||
raw.split('up ')[1].split(', ')[0];
|
raw.split('up ')[1].split(', ')[0];
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getTcp(ServerPrivateInfo spi, String raw) {
|
void _getTcp(ServerPrivateInfo spi, String raw) {
|
||||||
@@ -283,7 +278,6 @@ class ServerProvider extends BusyProvider {
|
|||||||
final vals = idx.split(RegExp(r'\s{1,}'));
|
final vals = idx.split(RegExp(r'\s{1,}'));
|
||||||
info.status.tcp = TcpStatus(vals[5].i, vals[6].i, vals[7].i, vals[8].i);
|
info.status.tcp = TcpStatus(vals[5].i, vals[6].i, vals[7].i, vals[8].i);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getDisk(ServerPrivateInfo spi, String raw) {
|
void _getDisk(ServerPrivateInfo spi, String raw) {
|
||||||
@@ -299,7 +293,6 @@ class ServerProvider extends BusyProvider {
|
|||||||
int.parse(vals[4].replaceFirst('%', '')), vals[2], vals[1], vals[3]));
|
int.parse(vals[4].replaceFirst('%', '')), vals[2], vals[1], vals[3]));
|
||||||
}
|
}
|
||||||
info.status.disk = list;
|
info.status.disk = list;
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getMem(ServerPrivateInfo spi, String raw) {
|
void _getMem(ServerPrivateInfo spi, String raw) {
|
||||||
@@ -318,7 +311,6 @@ class ServerProvider extends BusyProvider {
|
|||||||
avail: memList[5]);
|
avail: memList[5]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> runSnippet(ServerPrivateInfo spi, Snippet snippet) async {
|
Future<String?> runSnippet(ServerPrivateInfo spi, Snippet snippet) async {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class BuildData {
|
|||||||
static const String name = "ServerBox";
|
static const String name = "ServerBox";
|
||||||
static const int build = 97;
|
static const int build = 97;
|
||||||
static const String engine =
|
static const String engine =
|
||||||
"Flutter 2.10.0 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 5f105a6ca7 (7 days ago) • 2022-02-01 14:15:42 -0800\nEngine • revision 776efd2034\nTools • Dart 2.16.0 • DevTools 2.9.2\n";
|
"Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 77d935af4d (8 weeks ago) • 2021-12-16 08:37:33 -0800\nEngine • revision 890a5fca2e\nTools • Dart 2.15.1\n";
|
||||||
static const String buildAt = "2022-02-09 10:58:45.586008";
|
static const String buildAt = "2022-02-10 19:30:23.388434";
|
||||||
static const int modifications = 8;
|
static const int modifications = 9;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ class _MyHomePageState extends State<MyHomePage>
|
|||||||
animationDuration: const Duration(milliseconds: 300),
|
animationDuration: const Duration(milliseconds: 300),
|
||||||
animateChildDecoration: true,
|
animateChildDecoration: true,
|
||||||
rtlOpening: false,
|
rtlOpening: false,
|
||||||
disabledGestures: true,
|
|
||||||
childDecoration: const BoxDecoration(
|
childDecoration: const BoxDecoration(
|
||||||
// NOTICE: Uncomment if you want to add shadow behind the page.
|
// NOTICE: Uncomment if you want to add shadow behind the page.
|
||||||
// Keep in mind that it may cause animation jerks.
|
// Keep in mind that it may cause animation jerks.
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ class _PingPageState extends State<PingPage>
|
|||||||
RoundRectCard(
|
RoundRectCard(
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Padding(padding: const EdgeInsets.all(7), child: Text(_result)),
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(7), child: Text(_result)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import 'package:toolbox/data/store/setting.dart';
|
|||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
import 'package:toolbox/view/page/server/detail.dart';
|
import 'package:toolbox/view/page/server/detail.dart';
|
||||||
import 'package:toolbox/view/page/server/edit.dart';
|
import 'package:toolbox/view/page/server/edit.dart';
|
||||||
|
import 'package:toolbox/view/page/sftp.dart';
|
||||||
import 'package:toolbox/view/page/snippet/list.dart';
|
import 'package:toolbox/view/page/snippet/list.dart';
|
||||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
|
|
||||||
@@ -241,9 +242,16 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
switch (item) {
|
switch (item) {
|
||||||
case MenuItems.ssh:
|
case MenuItems.ssh:
|
||||||
case MenuItems.apt:
|
case MenuItems.apt:
|
||||||
case MenuItems.sftp:
|
|
||||||
showSnackBar(context, const Text('Now is not supported'));
|
showSnackBar(context, const Text('Now is not supported'));
|
||||||
break;
|
break;
|
||||||
|
case MenuItems.sftp:
|
||||||
|
AppRoute(
|
||||||
|
SFTPPage(
|
||||||
|
spi: spi,
|
||||||
|
),
|
||||||
|
'SFTP')
|
||||||
|
.go(context);
|
||||||
|
break;
|
||||||
case MenuItems.snippet:
|
case MenuItems.snippet:
|
||||||
AppRoute(
|
AppRoute(
|
||||||
SnippetListPage(
|
SnippetListPage(
|
||||||
|
|||||||
337
lib/view/page/sftp.dart
Normal file
337
lib/view/page/sftp.dart
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toolbox/core/utils.dart';
|
||||||
|
import 'package:toolbox/data/model/server/server_connection_state.dart';
|
||||||
|
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||||
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
|
import 'package:toolbox/locator.dart';
|
||||||
|
import 'package:toolbox/view/widget/fade_in.dart';
|
||||||
|
|
||||||
|
class SFTPPage extends StatefulWidget {
|
||||||
|
final ServerPrivateInfo? spi;
|
||||||
|
const SFTPPage({this.spi, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SFTPPageState createState() => _SFTPPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SFTPPageState extends State<SFTPPage> {
|
||||||
|
/// Whether the Left/Right Destination is selected.
|
||||||
|
final List<bool> _selectedDest = List<bool>.filled(2, false);
|
||||||
|
final List<ServerPrivateInfo?> _destSpi =
|
||||||
|
List<ServerPrivateInfo?>.filled(2, null);
|
||||||
|
final List<List<SftpName>?> _files = List<List<SftpName>?>.filled(2, null);
|
||||||
|
final List<String> _paths = List<String>.filled(2, '');
|
||||||
|
final List<SftpClient?> _clients = List<SftpClient?>.filled(2, null);
|
||||||
|
|
||||||
|
final ScrollController _leftScrollController = ScrollController();
|
||||||
|
final ScrollController _rightScrollController = ScrollController();
|
||||||
|
|
||||||
|
late MediaQueryData _media;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_media = MediaQuery.of(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.spi != null) {
|
||||||
|
_destSpi[0] = widget.spi;
|
||||||
|
_selectedDest[0] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(_titleText),
|
||||||
|
),
|
||||||
|
body: Row(
|
||||||
|
children: [
|
||||||
|
_buildSingleColumn(true),
|
||||||
|
const VerticalDivider(
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
_buildSingleColumn(false),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get _titleText {
|
||||||
|
List<String> titles = [
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
if (_selectedDest[0]) {
|
||||||
|
titles[0] = _destSpi[0]?.name ?? '';
|
||||||
|
}
|
||||||
|
if (_selectedDest[1]) {
|
||||||
|
titles[1] = _destSpi[1]?.name ?? '';
|
||||||
|
}
|
||||||
|
return titles[0] == '' || titles[1] == '' ? 'SFTP' : titles.join(' - ');
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSingleColumn(bool left) {
|
||||||
|
Widget child;
|
||||||
|
if (!_selectedDest[left ? 0 : 1]) {
|
||||||
|
child = _buildDestSelector(left);
|
||||||
|
} else {
|
||||||
|
child = _buildFileView(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: (_media.size.width - 2) / 2,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget get centerCircleLoading => Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: _media.size.height * 0.4,
|
||||||
|
),
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildFileView(bool left) {
|
||||||
|
final spi = _destSpi[left ? 0 : 1];
|
||||||
|
final si =
|
||||||
|
locator<ServerProvider>().servers.firstWhere((s) => s.info == spi);
|
||||||
|
final client = si.client;
|
||||||
|
if (client == null ||
|
||||||
|
si.connectionState != ServerConnectionState.connected) {
|
||||||
|
return centerCircleLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_files[left ? 0 : 1] == null) {
|
||||||
|
updatePath('/', left);
|
||||||
|
listDir(client, '/', left);
|
||||||
|
return centerCircleLoading;
|
||||||
|
} else {
|
||||||
|
return RefreshIndicator(
|
||||||
|
child: FadeIn(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _files[left ? 0 : 1]!.length,
|
||||||
|
controller: left ? _leftScrollController : _rightScrollController,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final file = _files[left ? 0 : 1]![index];
|
||||||
|
final isDir = file.attr.mode?.isDirectory ?? true;
|
||||||
|
return ListTile(
|
||||||
|
leading:
|
||||||
|
Icon(isDir ? Icons.folder : Icons.insert_drive_file),
|
||||||
|
title: Text(file.filename),
|
||||||
|
subtitle: isDir
|
||||||
|
? null
|
||||||
|
: Text((convertBytes(file.attr.size ?? 0))),
|
||||||
|
onTap: () {
|
||||||
|
if (isDir) {
|
||||||
|
updatePath(file.filename, left);
|
||||||
|
listDir(client, _paths[left ? 0 : 1], left);
|
||||||
|
} else {
|
||||||
|
// downloadFile(client, file.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: () => onItemLongPress(context, left, file));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
key: Key(_paths[left ? 0 : 1]),
|
||||||
|
),
|
||||||
|
onRefresh: () => listDir(client, _paths[left ? 0 : 1], left));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onItemLongPress(BuildContext context, bool left, SftpName file) {
|
||||||
|
showRoundDialog(
|
||||||
|
context,
|
||||||
|
'Action',
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.delete),
|
||||||
|
title: const Text('Delete'),
|
||||||
|
onTap: () => showRoundDialog(context, 'Confirm',
|
||||||
|
Text('Are you sure to delete ${file.filename}?'), [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: const Text(
|
||||||
|
'Delete',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.folder),
|
||||||
|
title: const Text('Create Folder'),
|
||||||
|
onTap: () => mkdir(context, left)),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(left ? Icons.arrow_forward : Icons.arrow_back),
|
||||||
|
title: const Text('Copy'),
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.edit),
|
||||||
|
title: const Text('Rename'),
|
||||||
|
onTap: () => rename(context, left, file),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.file_download),
|
||||||
|
title: const Text('Download'),
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mkdir(BuildContext context, bool left) {
|
||||||
|
final textController = TextEditingController();
|
||||||
|
showRoundDialog(
|
||||||
|
context,
|
||||||
|
'Create Folder',
|
||||||
|
TextField(
|
||||||
|
controller: textController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Folder Name',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (textController.text == '') {
|
||||||
|
showRoundDialog(context, 'Attention',
|
||||||
|
const Text('You need input a name.'), [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK')),
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_clients[left ? 0 : 1]!
|
||||||
|
.mkdir(_paths[left ? 0 : 1] + '/' + textController.text);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'Create',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rename(BuildContext context, bool left, SftpName file) {
|
||||||
|
final textController = TextEditingController();
|
||||||
|
showRoundDialog(
|
||||||
|
context,
|
||||||
|
'Create Folder',
|
||||||
|
TextField(
|
||||||
|
controller: textController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'New Name',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (textController.text == '') {
|
||||||
|
showRoundDialog(context, 'Attention',
|
||||||
|
const Text('You need input a name.'), [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK')),
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _clients[left ? 0 : 1]!
|
||||||
|
.rename(file.filename, textController.text);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'Create',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
String convertBytes(int bytes) {
|
||||||
|
const suffix = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
double value = bytes.toDouble();
|
||||||
|
int squareTimes = 0;
|
||||||
|
for (; value / 1024 > 1 && squareTimes < 3; squareTimes++) {
|
||||||
|
value /= 1024;
|
||||||
|
}
|
||||||
|
var finalValue = value.toStringAsFixed(1);
|
||||||
|
if (finalValue.endsWith('.0')) {
|
||||||
|
finalValue = finalValue.replaceFirst('.0', '');
|
||||||
|
}
|
||||||
|
return '$finalValue ${suffix[squareTimes]}';
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePath(String filename, bool left) {
|
||||||
|
if (filename == '..') {
|
||||||
|
_paths[left ? 0 : 1] = _paths[left ? 0 : 1]
|
||||||
|
.substring(0, _paths[left ? 0 : 1].lastIndexOf('/'));
|
||||||
|
if (_paths[left ? 0 : 1] == '') {
|
||||||
|
_paths[left ? 0 : 1] = '/';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_paths[left ? 0 : 1] = _paths[left ? 0 : 1] +
|
||||||
|
(_paths[left ? 0 : 1].endsWith('/') ? '' : '/') +
|
||||||
|
filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> listDir(SSHClient client, String path, bool left) async {
|
||||||
|
final sftpc = await client.sftp();
|
||||||
|
_clients[left ? 0 : 1] = sftpc;
|
||||||
|
final fs = await sftpc.listdir(path);
|
||||||
|
fs.sort((a, b) => a.filename.compareTo(b.filename));
|
||||||
|
fs.removeAt(0);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_files[left ? 0 : 1] = fs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDestSelector(bool left) {
|
||||||
|
return Column(
|
||||||
|
children: locator<ServerProvider>()
|
||||||
|
.servers
|
||||||
|
.map((e) => _buildDestSelectorItem(e.info, left))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDestSelectorItem(ServerPrivateInfo spi, bool left) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(spi.name),
|
||||||
|
subtitle: Text('${spi.user}@${spi.ip}:${spi.port}'),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_destSpi[left ? 0 : 1] = spi;
|
||||||
|
_selectedDest[left ? 0 : 1] = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
lib/view/widget/fade_in.dart
Normal file
44
lib/view/widget/fade_in.dart
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// 渐隐渐显实现
|
||||||
|
class FadeIn extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const FadeIn({Key? key, required this.child}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MyFadeInState createState() => _MyFadeInState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyFadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 377),
|
||||||
|
);
|
||||||
|
_animation = Tween(
|
||||||
|
begin: 0.0,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(_controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_controller.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_controller.forward();
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: _animation,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,13 +44,12 @@ Future<String> getFlutterVersion() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getBuildData() async {
|
Future<Map<String, dynamic>> getBuildData() async {
|
||||||
final modifiedCount = await getGitModificationCount();
|
|
||||||
final data = {
|
final data = {
|
||||||
'name': appName,
|
'name': appName,
|
||||||
'build': await getGitCommitCount() + (modifiedCount == 0 ? 0 : 1),
|
'build': await getGitCommitCount(),
|
||||||
'engine': await getFlutterVersion(),
|
'engine': await getFlutterVersion(),
|
||||||
'buildAt': DateTime.now().toString(),
|
'buildAt': DateTime.now().toString(),
|
||||||
'modifications': modifiedCount,
|
'modifications': await getGitModificationCount(),
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -84,8 +83,7 @@ void flutterRun(String? mode) {
|
|||||||
|
|
||||||
Future<void> flutterBuild(String source, String target, bool isAndroid) async {
|
Future<void> flutterBuild(String source, String target, bool isAndroid) async {
|
||||||
final startTime = DateTime.now();
|
final startTime = DateTime.now();
|
||||||
final build = await getGitCommitCount() +
|
final build = await getGitCommitCount();
|
||||||
(await getGitModificationCount() == 0 ? 0 : 1);
|
|
||||||
|
|
||||||
final args = [
|
final args = [
|
||||||
'build',
|
'build',
|
||||||
|
|||||||
Reference in New Issue
Block a user