mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
improved 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 = 166;
|
CURRENT_PROJECT_VERSION = 172;
|
||||||
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.166;
|
MARKETING_VERSION = 1.0.172;
|
||||||
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 = 166;
|
CURRENT_PROJECT_VERSION = 172;
|
||||||
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.166;
|
MARKETING_VERSION = 1.0.172;
|
||||||
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 = 166;
|
CURRENT_PROJECT_VERSION = 172;
|
||||||
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.166;
|
MARKETING_VERSION = 1.0.172;
|
||||||
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";
|
||||||
|
|||||||
@@ -1,29 +1,35 @@
|
|||||||
class AbsolutePath {
|
class AbsolutePath {
|
||||||
String path;
|
String _path;
|
||||||
String? _prePath;
|
String get path => _path;
|
||||||
AbsolutePath(this.path);
|
final List<String> _prePath;
|
||||||
|
|
||||||
|
AbsolutePath(this._path) : _prePath = ['/'];
|
||||||
|
|
||||||
void update(String newPath) {
|
void update(String newPath) {
|
||||||
_prePath = path;
|
_prePath.add(_path);
|
||||||
if (newPath == '..') {
|
if (newPath == '..') {
|
||||||
path = path.substring(0, path.lastIndexOf('/'));
|
_path = _path.substring(0, _path.lastIndexOf('/'));
|
||||||
if (path == '') {
|
if (_path == '') {
|
||||||
path = '/';
|
_path = '/';
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newPath == '/') {
|
if (newPath == '/') {
|
||||||
path = '/';
|
_path = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
path = path + (path.endsWith('/') ? '' : '/') + newPath;
|
if (newPath.startsWith('/')) {
|
||||||
|
_path = newPath;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_path = _path + (_path.endsWith('/') ? '' : '/') + newPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool undo() {
|
bool undo() {
|
||||||
if (_prePath == null || _prePath == path) {
|
if (_prePath.isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
path = _prePath!;
|
_path = _prePath.removeLast();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ 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 SftpBrowserStatus {
|
class SftpBrowserStatus {
|
||||||
bool selected = false;
|
|
||||||
ServerPrivateInfo? spi;
|
|
||||||
List<SftpName>? files;
|
List<SftpName>? files;
|
||||||
AbsolutePath? path;
|
AbsolutePath? path;
|
||||||
SftpClient? client;
|
SftpClient? client;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
class BuildData {
|
class BuildData {
|
||||||
static const String name = "ServerBox";
|
static const String name = "ServerBox";
|
||||||
static const int build = 169;
|
static const int build = 173;
|
||||||
static const String engine =
|
static const String engine =
|
||||||
"Flutter 3.3.9 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision b8f7f1f986 (3 weeks ago) • 2022-11-23 06:43:51 +0900\nEngine • revision 8f2221fbef\nTools • Dart 2.18.5 • DevTools 2.15.0\n";
|
"Flutter 3.3.9 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision b8f7f1f986 (4 weeks ago) • 2022-11-23 06:43:51 +0900\nEngine • revision 8f2221fbef\nTools • Dart 2.18.5 • DevTools 2.15.0\n";
|
||||||
static const String buildAt = "2022-12-11 12:36:17.737879";
|
static const String buildAt = "2022-12-20 15:01:02.224953";
|
||||||
static const int modifications = 8;
|
static const int modifications = 12;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"go": MessageLookupByLibrary.simpleMessage("Go"),
|
"go": MessageLookupByLibrary.simpleMessage("Go"),
|
||||||
"goSftpDlPage":
|
"goSftpDlPage":
|
||||||
MessageLookupByLibrary.simpleMessage("Go to SFTP download page?"),
|
MessageLookupByLibrary.simpleMessage("Go to SFTP download page?"),
|
||||||
|
"goto": MessageLookupByLibrary.simpleMessage("Go to"),
|
||||||
"host": MessageLookupByLibrary.simpleMessage("Host"),
|
"host": MessageLookupByLibrary.simpleMessage("Host"),
|
||||||
"httpFailedWithCode": m6,
|
"httpFailedWithCode": m6,
|
||||||
"imagesList": MessageLookupByLibrary.simpleMessage("Images list"),
|
"imagesList": MessageLookupByLibrary.simpleMessage("Images list"),
|
||||||
@@ -175,6 +176,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
MessageLookupByLibrary.simpleMessage("No update available"),
|
MessageLookupByLibrary.simpleMessage("No update available"),
|
||||||
"ok": MessageLookupByLibrary.simpleMessage("OK"),
|
"ok": MessageLookupByLibrary.simpleMessage("OK"),
|
||||||
"open": MessageLookupByLibrary.simpleMessage("Open"),
|
"open": MessageLookupByLibrary.simpleMessage("Open"),
|
||||||
|
"path": MessageLookupByLibrary.simpleMessage("Path"),
|
||||||
"ping": MessageLookupByLibrary.simpleMessage("Ping"),
|
"ping": MessageLookupByLibrary.simpleMessage("Ping"),
|
||||||
"pingAvg": MessageLookupByLibrary.simpleMessage("Avg:"),
|
"pingAvg": MessageLookupByLibrary.simpleMessage("Avg:"),
|
||||||
"pingInputIP": MessageLookupByLibrary.simpleMessage(
|
"pingInputIP": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"foundNUpdate": m5,
|
"foundNUpdate": m5,
|
||||||
"go": MessageLookupByLibrary.simpleMessage("开始"),
|
"go": MessageLookupByLibrary.simpleMessage("开始"),
|
||||||
"goSftpDlPage": MessageLookupByLibrary.simpleMessage("前往下载页?"),
|
"goSftpDlPage": MessageLookupByLibrary.simpleMessage("前往下载页?"),
|
||||||
|
"goto": MessageLookupByLibrary.simpleMessage("前往"),
|
||||||
"host": MessageLookupByLibrary.simpleMessage("主机"),
|
"host": MessageLookupByLibrary.simpleMessage("主机"),
|
||||||
"httpFailedWithCode": m6,
|
"httpFailedWithCode": m6,
|
||||||
"imagesList": MessageLookupByLibrary.simpleMessage("镜像列表"),
|
"imagesList": MessageLookupByLibrary.simpleMessage("镜像列表"),
|
||||||
@@ -154,6 +155,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"noUpdateAvailable": MessageLookupByLibrary.simpleMessage("没有可用更新"),
|
"noUpdateAvailable": MessageLookupByLibrary.simpleMessage("没有可用更新"),
|
||||||
"ok": MessageLookupByLibrary.simpleMessage("好"),
|
"ok": MessageLookupByLibrary.simpleMessage("好"),
|
||||||
"open": MessageLookupByLibrary.simpleMessage("打开"),
|
"open": MessageLookupByLibrary.simpleMessage("打开"),
|
||||||
|
"path": MessageLookupByLibrary.simpleMessage("路径"),
|
||||||
"ping": MessageLookupByLibrary.simpleMessage("Ping"),
|
"ping": MessageLookupByLibrary.simpleMessage("Ping"),
|
||||||
"pingAvg": MessageLookupByLibrary.simpleMessage("平均:"),
|
"pingAvg": MessageLookupByLibrary.simpleMessage("平均:"),
|
||||||
"pingInputIP": MessageLookupByLibrary.simpleMessage("请输入目标IP或域名"),
|
"pingInputIP": MessageLookupByLibrary.simpleMessage("请输入目标IP或域名"),
|
||||||
|
|||||||
@@ -1480,6 +1480,26 @@ class S {
|
|||||||
args: [count],
|
args: [count],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Path`
|
||||||
|
String get path {
|
||||||
|
return Intl.message(
|
||||||
|
'Path',
|
||||||
|
name: 'path',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Go to`
|
||||||
|
String get goto {
|
||||||
|
return Intl.message(
|
||||||
|
'Go to',
|
||||||
|
name: 'goto',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||||
|
|||||||
@@ -141,5 +141,7 @@
|
|||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"isBusy": "Is busy now",
|
"isBusy": "Is busy now",
|
||||||
"imagesList": "Images list",
|
"imagesList": "Images list",
|
||||||
"dockerImagesFmt": "{count} images"
|
"dockerImagesFmt": "{count} images",
|
||||||
|
"path": "Path",
|
||||||
|
"goto": "Go to"
|
||||||
}
|
}
|
||||||
@@ -141,5 +141,7 @@
|
|||||||
"preview": "预览",
|
"preview": "预览",
|
||||||
"isBusy": "当前正忙",
|
"isBusy": "当前正忙",
|
||||||
"imagesList": "镜像列表",
|
"imagesList": "镜像列表",
|
||||||
"dockerImagesFmt": "共 {count} 个镜像"
|
"dockerImagesFmt": "共 {count} 个镜像",
|
||||||
|
"path": "路径",
|
||||||
|
"goto": "前往"
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:toolbox/core/route.dart';
|
import 'package:toolbox/core/route.dart';
|
||||||
import 'package:toolbox/data/provider/private_key.dart';
|
import 'package:toolbox/data/provider/private_key.dart';
|
||||||
import 'package:toolbox/data/res/color.dart';
|
|
||||||
import 'package:toolbox/data/res/font_style.dart';
|
import 'package:toolbox/data/res/font_style.dart';
|
||||||
import 'package:toolbox/data/res/padding.dart';
|
import 'package:toolbox/data/res/padding.dart';
|
||||||
import 'package:toolbox/generated/l10n.dart';
|
import 'package:toolbox/generated/l10n.dart';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toolbox/core/extension/colorx.dart';
|
|
||||||
import 'package:toolbox/core/extension/numx.dart';
|
import 'package:toolbox/core/extension/numx.dart';
|
||||||
import 'package:toolbox/core/extension/stringx.dart';
|
import 'package:toolbox/core/extension/stringx.dart';
|
||||||
import 'package:toolbox/core/route.dart';
|
import 'package:toolbox/core/route.dart';
|
||||||
|
|||||||
@@ -31,12 +31,14 @@ class SFTPPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _SFTPPageState extends State<SFTPPage> {
|
class _SFTPPageState extends State<SFTPPage> {
|
||||||
final SftpBrowserStatus _status = SftpBrowserStatus();
|
final SftpBrowserStatus _status = SftpBrowserStatus();
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
late MediaQueryData _media;
|
late MediaQueryData _media;
|
||||||
late S _s;
|
late S _s;
|
||||||
|
|
||||||
|
ServerInfo? _si;
|
||||||
|
SSHClient? _client;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
@@ -47,8 +49,9 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_status.spi = widget.spi;
|
final serverProvider = locator<ServerProvider>();
|
||||||
_status.selected = true;
|
_si = serverProvider.servers.firstWhere((s) => s.info == widget.spi);
|
||||||
|
_client = _si?.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -86,9 +89,73 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: _buildFileView(),
|
body: _buildFileView(),
|
||||||
|
bottomNavigationBar: _buildPath(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildPath() {
|
||||||
|
return SafeArea(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
(_status.path?.path ?? _s.loadingFiles).omitStartStr(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
onPressed: () async {
|
||||||
|
await backward();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
onPressed: () async {
|
||||||
|
final p = await showRoundDialog<String?>(
|
||||||
|
context,
|
||||||
|
_s.goto,
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: _s.path,
|
||||||
|
hintText: '/',
|
||||||
|
),
|
||||||
|
onSubmitted: (value) =>
|
||||||
|
Navigator.of(context).pop(value),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: Text(_s.cancel))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (p != null) {
|
||||||
|
if (p.isEmpty) {
|
||||||
|
showSnackBar(context, Text(_s.fieldMustNotEmpty));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_status.path?.update(p);
|
||||||
|
listDir(path: p);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.gps_fixed),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Widget get centerCircleLoading => Center(
|
Widget get centerCircleLoading => Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -101,48 +168,36 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildFileView() {
|
Widget _buildFileView() {
|
||||||
if (!_status.selected) {
|
if (_client == null ||
|
||||||
return ListView(
|
_si?.connectionState != ServerConnectionState.connected) {
|
||||||
children: [
|
return centerCircleLoading;
|
||||||
_buildDestSelector(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
final spi = _status.spi;
|
|
||||||
final si =
|
if (_status.isBusy) {
|
||||||
locator<ServerProvider>().servers.firstWhere((s) => s.info == spi);
|
|
||||||
final client = si.client;
|
|
||||||
if (client == null ||
|
|
||||||
si.connectionState != ServerConnectionState.connected) {
|
|
||||||
return centerCircleLoading;
|
return centerCircleLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_status.files == null) {
|
if (_status.files == null) {
|
||||||
_status.path = AbsolutePath('/');
|
_status.path = AbsolutePath('/');
|
||||||
listDir(path: '/', client: client);
|
listDir(path: '/', client: _client);
|
||||||
return centerCircleLoading;
|
return centerCircleLoading;
|
||||||
} else {
|
} else {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
child: FadeIn(
|
child: FadeIn(
|
||||||
key: Key(_status.spi!.name + _status.path!.path),
|
key: Key(widget.spi.name + _status.path!.path),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: _status.files!.length + 1,
|
itemCount: _status.files!.length,
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == 0) {
|
final file = _status.files![index];
|
||||||
return _buildDestSelector();
|
|
||||||
}
|
|
||||||
final file = _status.files![index - 1];
|
|
||||||
final isDir = file.attr.isDirectory;
|
final isDir = file.attr.isDirectory;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
|
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
|
||||||
title: Text(file.filename),
|
title: Text(file.filename),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
'${getTime(file.attr.modifyTime)}\n${getMode(file.attr.mode)}',
|
||||||
(file.attr.modifyTime ?? 0) * 1000)
|
|
||||||
.toString()
|
|
||||||
.replaceFirst('.000', ''),
|
|
||||||
style: const TextStyle(color: Colors.grey),
|
style: const TextStyle(color: Colors.grey),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
),
|
),
|
||||||
subtitle:
|
subtitle:
|
||||||
isDir ? null : Text((file.attr.size ?? 0).convertBytes),
|
isDir ? null : Text((file.attr.size ?? 0).convertBytes),
|
||||||
@@ -164,6 +219,30 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getTime(int? unixMill) {
|
||||||
|
return DateTime.fromMillisecondsSinceEpoch((unixMill ?? 0) * 1000)
|
||||||
|
.toString()
|
||||||
|
.replaceFirst('.000', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMode(SftpFileMode? mode) {
|
||||||
|
if (mode == null) {
|
||||||
|
return '---';
|
||||||
|
}
|
||||||
|
|
||||||
|
final user = getRoleMode(mode.userRead, mode.userWrite, mode.userExecute);
|
||||||
|
final group =
|
||||||
|
getRoleMode(mode.groupRead, mode.groupWrite, mode.groupExecute);
|
||||||
|
final other =
|
||||||
|
getRoleMode(mode.otherRead, mode.otherWrite, mode.otherExecute);
|
||||||
|
|
||||||
|
return '$user$group$other';
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRoleMode(bool r, bool w, bool x) {
|
||||||
|
return '${r ? 'r' : '-'}${w ? 'w' : '-'}${x ? 'x' : '-'}';
|
||||||
|
}
|
||||||
|
|
||||||
void onItemPress(BuildContext context, SftpName file, bool showDownload) {
|
void onItemPress(BuildContext context, SftpName file, bool showDownload) {
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
context,
|
context,
|
||||||
@@ -214,11 +293,11 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
final remotePath =
|
final remotePath =
|
||||||
prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
|
prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
|
||||||
final local = '${(await sftpDownloadDir).path}$remotePath';
|
final local = '${(await sftpDownloadDir).path}$remotePath';
|
||||||
final pubKeyId = _status.spi!.pubKeyId;
|
final pubKeyId = widget.spi.pubKeyId;
|
||||||
|
|
||||||
locator<SftpDownloadProvider>().add(
|
locator<SftpDownloadProvider>().add(
|
||||||
DownloadItem(
|
DownloadItem(
|
||||||
_status.spi!,
|
widget.spi,
|
||||||
remotePath,
|
remotePath,
|
||||||
local,
|
local,
|
||||||
),
|
),
|
||||||
@@ -429,7 +508,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final fs =
|
final fs =
|
||||||
await _status.client!.listdir(path ?? (_status.path?.path ?? '/'));
|
await _status.client!.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) {
|
||||||
@@ -450,41 +529,13 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
await backward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> backward() async {
|
||||||
if (_status.path!.undo()) {
|
if (_status.path!.undo()) {
|
||||||
await listDir();
|
await listDir();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDestSelector() {
|
|
||||||
final str = _status.path?.path;
|
|
||||||
return ExpansionTile(
|
|
||||||
title: Text(_status.spi?.name ?? _s.chooseDestination),
|
|
||||||
subtitle: _status.selected
|
|
||||||
? str!.omitStartStr(style: const TextStyle(color: Colors.grey))
|
|
||||||
: null,
|
|
||||||
children: locator<ServerProvider>()
|
|
||||||
.servers
|
|
||||||
.map((e) => _buildDestSelectorItem(e.info))
|
|
||||||
.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDestSelectorItem(ServerPrivateInfo spi) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text(spi.name),
|
|
||||||
subtitle: Text('${spi.user}@${spi.ip}:${spi.port}'),
|
|
||||||
onTap: () {
|
|
||||||
_status.spi = spi;
|
|
||||||
_status.selected = true;
|
|
||||||
_status.path = AbsolutePath('/');
|
|
||||||
listDir(
|
|
||||||
client: locator<ServerProvider>()
|
|
||||||
.servers
|
|
||||||
.firstWhere((s) => s.info == spi)
|
|
||||||
.client,
|
|
||||||
path: '/',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user