mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
New feat
- SFTP download - open downloaded files in other apps
This commit is contained in:
@@ -8,6 +8,8 @@ PODS:
|
||||
- Flutter
|
||||
- r_upgrade (0.0.1):
|
||||
- Flutter
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
@@ -17,6 +19,7 @@ DEPENDENCIES:
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- r_upgrade (from `.symlinks/plugins/r_upgrade/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -30,6 +33,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||
r_upgrade:
|
||||
:path: ".symlinks/plugins/r_upgrade/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
@@ -39,6 +44,7 @@ SPEC CHECKSUMS:
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
||||
|
||||
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
|
||||
|
||||
@@ -354,7 +354,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 125;
|
||||
CURRENT_PROJECT_VERSION = 126;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -362,7 +362,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.125;
|
||||
MARKETING_VERSION = 1.0.126;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -484,7 +484,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 125;
|
||||
CURRENT_PROJECT_VERSION = 126;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -492,7 +492,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.125;
|
||||
MARKETING_VERSION = 1.0.126;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -508,7 +508,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 125;
|
||||
CURRENT_PROJECT_VERSION = 126;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -516,7 +516,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.125;
|
||||
MARKETING_VERSION = 1.0.126;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
11
lib/core/extension/colorx.dart
Normal file
11
lib/core/extension/colorx.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension ColorX on Color {
|
||||
bool get isBrightColor {
|
||||
return getBrightnessFromColor == Brightness.light;
|
||||
}
|
||||
|
||||
Brightness get getBrightnessFromColor {
|
||||
return ThemeData.estimateBrightnessForColor(this);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/data/model/distribution.dart';
|
||||
|
||||
extension StringX on String {
|
||||
@@ -21,4 +22,40 @@ extension StringX on String {
|
||||
Uri get uri {
|
||||
return Uri.parse(this);
|
||||
}
|
||||
|
||||
Widget omitStartStr(
|
||||
{TextStyle? style, TextOverflow? overflow, int? maxLines}) {
|
||||
return LayoutBuilder(builder: (context, size) {
|
||||
bool exceeded = false;
|
||||
int len = 0;
|
||||
for (; !exceeded && len < length; len++) {
|
||||
// Build the textspan
|
||||
var span = TextSpan(
|
||||
text: 'A' * 7 + substring(length - len),
|
||||
style: style ?? Theme.of(context).textTheme.bodyText2,
|
||||
);
|
||||
|
||||
// Use a textpainter to determine if it will exceed max lines
|
||||
var tp = TextPainter(
|
||||
maxLines: maxLines ?? 1,
|
||||
textDirection: TextDirection.ltr,
|
||||
text: span,
|
||||
);
|
||||
|
||||
// trigger it to layout
|
||||
tp.layout(maxWidth: size.maxWidth);
|
||||
|
||||
// whether the text overflowed or not
|
||||
exceeded = tp.didExceedMaxLines;
|
||||
}
|
||||
|
||||
return Text(
|
||||
(exceeded ? '...' : '') + substring(length - len),
|
||||
overflow: overflow ?? TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: maxLines ?? 1,
|
||||
style: style,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
Future<Directory> get sftpDownloadDir async {
|
||||
final docDir = await getApplicationDocumentsDirectory();
|
||||
final dir = Directory('${docDir.path}/sftp');
|
||||
if (!dir.existsSync()) {
|
||||
dir.createSync();
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
38
lib/data/model/app/path_with_prefix.dart
Normal file
38
lib/data/model/app/path_with_prefix.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
class PathWithPrefix {
|
||||
late String _prefixPath;
|
||||
String _path = '/';
|
||||
String? _prePath;
|
||||
String get path => _prefixPath + _path;
|
||||
|
||||
PathWithPrefix(String prefixPath) {
|
||||
if (prefixPath.endsWith('/')) {
|
||||
_prefixPath = prefixPath.substring(0, prefixPath.length - 1);
|
||||
} else {
|
||||
_prefixPath = prefixPath;
|
||||
}
|
||||
}
|
||||
|
||||
void update(String newPath) {
|
||||
_prePath = _path;
|
||||
if (newPath == '..') {
|
||||
_path = _path.substring(0, _path.lastIndexOf('/'));
|
||||
if (_path == '') {
|
||||
_path = '/';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (newPath == '/') {
|
||||
_path = '/';
|
||||
return;
|
||||
}
|
||||
_path = _path + (_path.endsWith('/') ? '' : '/') + newPath;
|
||||
}
|
||||
|
||||
bool undo() {
|
||||
if (_prePath == null || _path == _prePath) {
|
||||
return false;
|
||||
}
|
||||
_path = _prePath!;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class AbsolutePath {
|
||||
}
|
||||
|
||||
bool undo() {
|
||||
if (_prePath == null) {
|
||||
if (_prePath == null || _prePath == path) {
|
||||
return false;
|
||||
}
|
||||
path = _prePath!;
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/model/sftp/absolute_path.dart';
|
||||
|
||||
class SFTPSideViewStatus {
|
||||
class SftpBrowserStatus {
|
||||
bool selected = false;
|
||||
ServerPrivateInfo? spi;
|
||||
List<SftpName>? files;
|
||||
@@ -10,5 +10,5 @@ class SFTPSideViewStatus {
|
||||
SftpClient? client;
|
||||
bool isBusy = false;
|
||||
|
||||
SFTPSideViewStatus();
|
||||
SftpBrowserStatus();
|
||||
}
|
||||
55
lib/data/model/sftp/download_status.dart
Normal file
55
lib/data/model/sftp/download_status.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:toolbox/data/model/sftp/download_worker.dart';
|
||||
|
||||
class SftpDownloadStatus {
|
||||
final int id;
|
||||
final DownloadItem item;
|
||||
final void Function() notifyListeners;
|
||||
late SftpDownloadWorker worker;
|
||||
|
||||
String get fileName => item.localPath.split('/').last;
|
||||
|
||||
// status of the download
|
||||
double? progress;
|
||||
SftpWorkerStatus? status;
|
||||
int? size;
|
||||
Exception? error;
|
||||
Duration? spentTime;
|
||||
|
||||
SftpDownloadStatus(this.item, this.notifyListeners, {String? key})
|
||||
: id = DateTime.now().microsecondsSinceEpoch {
|
||||
worker =
|
||||
SftpDownloadWorker(onNotify: onNotify, item: item, privateKey: key);
|
||||
worker.init();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is SftpDownloadStatus && id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id ^ super.hashCode;
|
||||
|
||||
void onNotify(dynamic event) {
|
||||
switch (event.runtimeType) {
|
||||
case SftpWorkerStatus:
|
||||
status = event;
|
||||
break;
|
||||
case double:
|
||||
progress = event;
|
||||
break;
|
||||
case int:
|
||||
size = event;
|
||||
break;
|
||||
case Exception:
|
||||
error = event;
|
||||
break;
|
||||
case Duration:
|
||||
spentTime = event;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
enum SftpWorkerStatus { preparing, sshConnectted, downloading, finished }
|
||||
107
lib/data/model/sftp/download_worker.dart
Normal file
107
lib/data/model/sftp/download_worker.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:easy_isolate/easy_isolate.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/model/sftp/download_status.dart';
|
||||
|
||||
class DownloadItem {
|
||||
DownloadItem(this.spi, this.remotePath, this.localPath);
|
||||
|
||||
final ServerPrivateInfo spi;
|
||||
final String remotePath;
|
||||
final String localPath;
|
||||
}
|
||||
|
||||
class SftpDownloadWorker {
|
||||
SftpDownloadWorker(
|
||||
{required this.onNotify, required this.item, this.privateKey});
|
||||
|
||||
final Function(Object event) onNotify;
|
||||
final DownloadItem item;
|
||||
final worker = Worker();
|
||||
final String? privateKey;
|
||||
|
||||
/// Initiate the worker (new thread) and start listen from messages between
|
||||
/// the threads
|
||||
Future<void> init() async {
|
||||
if (worker.isInitialized) worker.dispose();
|
||||
await worker.init(
|
||||
mainMessageHandler,
|
||||
isolateMessageHandler,
|
||||
errorHandler: print,
|
||||
);
|
||||
worker.sendMessage(DownloadItemEvent(item, privateKey));
|
||||
}
|
||||
|
||||
/// Handle the messages coming from the isolate
|
||||
void mainMessageHandler(dynamic data, SendPort isolateSendPort) {
|
||||
onNotify(data);
|
||||
}
|
||||
|
||||
/// Handle the messages coming from the main
|
||||
static isolateMessageHandler(
|
||||
dynamic data, SendPort mainSendPort, SendErrorFunction sendError) async {
|
||||
if (data is DownloadItemEvent) {
|
||||
try {
|
||||
mainSendPort.send(SftpWorkerStatus.preparing);
|
||||
final watch = Stopwatch()..start();
|
||||
final item = data.item;
|
||||
final spi = item.spi;
|
||||
final socket = await SSHSocket.connect(spi.ip, spi.port);
|
||||
SSHClient client;
|
||||
if (spi.pubKeyId == null) {
|
||||
client = SSHClient(socket,
|
||||
username: spi.user,
|
||||
onPasswordRequest: () => spi.authorization as String);
|
||||
} else {
|
||||
client = SSHClient(socket,
|
||||
username: spi.user,
|
||||
identities: SSHKeyPair.fromPem(data.privateKey!));
|
||||
}
|
||||
mainSendPort.send(SftpWorkerStatus.sshConnectted);
|
||||
|
||||
final remotePath = item.remotePath;
|
||||
final localPath = item.localPath;
|
||||
await Directory(localPath.substring(0, item.localPath.lastIndexOf('/')))
|
||||
.create(recursive: true);
|
||||
final local = File(localPath);
|
||||
if (await local.exists()) {
|
||||
await local.delete();
|
||||
}
|
||||
final localFile = local.openWrite(mode: FileMode.append);
|
||||
final file = await (await client.sftp()).open(remotePath);
|
||||
final size = (await file.stat()).size;
|
||||
if (size == null) {
|
||||
mainSendPort.send(Exception('can not get file size'));
|
||||
return;
|
||||
}
|
||||
const defaultChunkSize = 1024 * 512;
|
||||
final chunkSize = size > defaultChunkSize ? defaultChunkSize : size;
|
||||
mainSendPort.send(size);
|
||||
mainSendPort.send(SftpWorkerStatus.downloading);
|
||||
for (var i = 0; i < size; i += chunkSize) {
|
||||
final fileData = file.read(length: chunkSize);
|
||||
await for (var form in fileData) {
|
||||
localFile.add(form);
|
||||
mainSendPort.send((i + form.length) / size * 100);
|
||||
}
|
||||
}
|
||||
mainSendPort.send(SftpWorkerStatus.finished);
|
||||
localFile.close();
|
||||
mainSendPort.send(watch.elapsed);
|
||||
} catch (e) {
|
||||
mainSendPort.send(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadItemEvent {
|
||||
DownloadItemEvent(this.item, this.privateKey);
|
||||
|
||||
final DownloadItem item;
|
||||
final String? privateKey;
|
||||
}
|
||||
31
lib/data/provider/sftp_download.dart
Normal file
31
lib/data/provider/sftp_download.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:toolbox/core/provider_base.dart';
|
||||
import 'package:toolbox/data/model/sftp/download_status.dart';
|
||||
import 'package:toolbox/data/model/sftp/download_worker.dart';
|
||||
|
||||
class SftpDownloadProvider extends ProviderBase {
|
||||
final List<SftpDownloadStatus> _status = [];
|
||||
List<SftpDownloadStatus> get status => _status;
|
||||
|
||||
List<SftpDownloadStatus> gets({int? id, String? fileName}) {
|
||||
var found = <SftpDownloadStatus>[];
|
||||
if (id != null) {
|
||||
found = _status.where((e) => e.id == id).toList();
|
||||
}
|
||||
if (fileName != null) {
|
||||
found = found
|
||||
.where((e) => e.item.localPath.split('/').last == fileName)
|
||||
.toList();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
SftpDownloadStatus? get({int? id, String? name}) {
|
||||
final found = gets(id: id, fileName: name);
|
||||
if (found.isEmpty) return null;
|
||||
return found.first;
|
||||
}
|
||||
|
||||
void add(DownloadItem item, {String? key}) {
|
||||
_status.add(SftpDownloadStatus(item, notifyListeners, key: key));
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
class BuildData {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 125;
|
||||
static const int build = 126;
|
||||
static const String engine =
|
||||
"Flutter 2.10.5 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 5464c5bac7 (2 weeks ago) • 2022-04-18 09:55:37 -0700\nEngine • revision 57d3bac3dd\nTools • Dart 2.16.2 • DevTools 2.9.2\n";
|
||||
static const String buildAt = "2022-05-05 16:11:07.575227";
|
||||
static const int modifications = 2;
|
||||
"Flutter 2.10.5 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 5464c5bac7 (3 weeks ago) • 2022-04-18 09:55:37 -0700\nEngine • revision 57d3bac3dd\nTools • Dart 2.16.2 • DevTools 2.9.2\n";
|
||||
static const String buildAt = "2022-05-07 21:49:09.473227";
|
||||
static const int modifications = 11;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const TextStyle size18 = TextStyle(fontSize: 18);
|
||||
const TextStyle grey = TextStyle(color: Colors.grey);
|
||||
|
||||
10
lib/data/res/path.dart
Normal file
10
lib/data/res/path.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
Future<Directory> get docDir async => await getApplicationDocumentsDirectory();
|
||||
|
||||
Future<Directory> get sftpDownloadDir async {
|
||||
final dir = Directory('${(await docDir).path}/sftp');
|
||||
return dir.create(recursive: true);
|
||||
}
|
||||
@@ -23,17 +23,30 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m0(rainSunMeGithub) =>
|
||||
"\nThanks ${rainSunMeGithub} for participating in the test.\n\nAll rights reserved.";
|
||||
|
||||
static String m1(code) => "request failed, status code: ${code}";
|
||||
static String m1(fileName) => "Download [${fileName}] to local?";
|
||||
|
||||
static String m2(myGithub) => "\nMade with ❤️ by ${myGithub}";
|
||||
static String m2(runningCount, stoppedCount) =>
|
||||
"${runningCount} running, ${stoppedCount} container stopped.";
|
||||
|
||||
static String m3(server) => "Are you sure to delete server [${server}]?";
|
||||
static String m3(count) => "${count} container running.";
|
||||
|
||||
static String m4(build) => "Found: v1.0.${build}, click to update";
|
||||
static String m4(percent, size) => "${percent}% of ${size}";
|
||||
|
||||
static String m5(build) => "Current: v1.0.${build}";
|
||||
static String m5(code) => "request failed, status code: ${code}";
|
||||
|
||||
static String m6(build) => "Current: v1.0.${build}, is up to date";
|
||||
static String m6(myGithub) => "\nMade with ❤️ by ${myGithub}";
|
||||
|
||||
static String m7(time) => "Spent time: ${time} seconds";
|
||||
|
||||
static String m8(name) => "Are you sure to delete [${name}]?";
|
||||
|
||||
static String m9(server) => "Are you sure to delete server [${server}]?";
|
||||
|
||||
static String m10(build) => "Found: v1.0.${build}, click to update";
|
||||
|
||||
static String m11(build) => "Current: v1.0.${build}";
|
||||
|
||||
static String m12(build) => "Current: v1.0.${build}, is up to date";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
@@ -41,9 +54,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"addAServer": MessageLookupByLibrary.simpleMessage("add a server"),
|
||||
"addPrivateKey":
|
||||
MessageLookupByLibrary.simpleMessage("Add private key"),
|
||||
"alreadyLastDir":
|
||||
MessageLookupByLibrary.simpleMessage("Already in last directory."),
|
||||
"appPrimaryColor":
|
||||
MessageLookupByLibrary.simpleMessage("App primary color"),
|
||||
"attention": MessageLookupByLibrary.simpleMessage("Attention"),
|
||||
"backDir": MessageLookupByLibrary.simpleMessage("Back"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
|
||||
"choose": MessageLookupByLibrary.simpleMessage("Choose"),
|
||||
"chooseDestination":
|
||||
@@ -52,33 +68,57 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Choose private key"),
|
||||
"clear": MessageLookupByLibrary.simpleMessage("Clear"),
|
||||
"close": MessageLookupByLibrary.simpleMessage("Close"),
|
||||
"containerStatus":
|
||||
MessageLookupByLibrary.simpleMessage("Container status"),
|
||||
"convert": MessageLookupByLibrary.simpleMessage("Convert"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||
"copyPath": MessageLookupByLibrary.simpleMessage("Copy path"),
|
||||
"createFolder": MessageLookupByLibrary.simpleMessage("Create folder"),
|
||||
"currentMode": MessageLookupByLibrary.simpleMessage("Current Mode"),
|
||||
"debug": MessageLookupByLibrary.simpleMessage("Debug"),
|
||||
"decode": MessageLookupByLibrary.simpleMessage("Decode"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
|
||||
"dl2Local": m1,
|
||||
"dockerStatusRunningAndStoppedFmt": m2,
|
||||
"dockerStatusRunningFmt": m3,
|
||||
"dockerWaitConnection": MessageLookupByLibrary.simpleMessage(
|
||||
"Please wait for the connection to be established."),
|
||||
"download": MessageLookupByLibrary.simpleMessage("Download"),
|
||||
"downloadFinished":
|
||||
MessageLookupByLibrary.simpleMessage("Download finished"),
|
||||
"downloadStatus": m4,
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Edit"),
|
||||
"encode": MessageLookupByLibrary.simpleMessage("Encode"),
|
||||
"error": MessageLookupByLibrary.simpleMessage("Error"),
|
||||
"exampleName": MessageLookupByLibrary.simpleMessage("Example name"),
|
||||
"export": MessageLookupByLibrary.simpleMessage("Export"),
|
||||
"fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage(
|
||||
"These fields must not be empty."),
|
||||
"go": MessageLookupByLibrary.simpleMessage("Go"),
|
||||
"goSftpDlPage":
|
||||
MessageLookupByLibrary.simpleMessage("Go to SFTP download page?"),
|
||||
"host": MessageLookupByLibrary.simpleMessage("Host"),
|
||||
"httpFailedWithCode": m1,
|
||||
"httpFailedWithCode": m5,
|
||||
"import": MessageLookupByLibrary.simpleMessage("Import"),
|
||||
"importAndExport":
|
||||
MessageLookupByLibrary.simpleMessage("Import and Export"),
|
||||
"install": MessageLookupByLibrary.simpleMessage("install"),
|
||||
"installDockerWithUrl": MessageLookupByLibrary.simpleMessage(
|
||||
"Please https://docs.docker.com/engine/install docker first."),
|
||||
"keepForeground":
|
||||
MessageLookupByLibrary.simpleMessage("Keep app foreground!"),
|
||||
"keyAuth": MessageLookupByLibrary.simpleMessage("Key Auth"),
|
||||
"launchPage": MessageLookupByLibrary.simpleMessage("Launch page"),
|
||||
"license": MessageLookupByLibrary.simpleMessage("License"),
|
||||
"loadingFiles":
|
||||
MessageLookupByLibrary.simpleMessage("Loading files..."),
|
||||
"loss": MessageLookupByLibrary.simpleMessage("Loss"),
|
||||
"madeWithLove": m2,
|
||||
"madeWithLove": m6,
|
||||
"max": MessageLookupByLibrary.simpleMessage("max"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("min"),
|
||||
"ms": MessageLookupByLibrary.simpleMessage("ms"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("Name"),
|
||||
"noClient": MessageLookupByLibrary.simpleMessage("No client"),
|
||||
"noResult": MessageLookupByLibrary.simpleMessage("No result"),
|
||||
"noSavedPrivateKey":
|
||||
MessageLookupByLibrary.simpleMessage("No saved private keys."),
|
||||
@@ -87,6 +127,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"noServerAvailable":
|
||||
MessageLookupByLibrary.simpleMessage("No server available."),
|
||||
"ok": MessageLookupByLibrary.simpleMessage("OK"),
|
||||
"open": MessageLookupByLibrary.simpleMessage("Open"),
|
||||
"ping": MessageLookupByLibrary.simpleMessage("Ping"),
|
||||
"pingAvg": MessageLookupByLibrary.simpleMessage("Avg:"),
|
||||
"pingInputIP": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -100,6 +141,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"port": MessageLookupByLibrary.simpleMessage("Port"),
|
||||
"privateKey": MessageLookupByLibrary.simpleMessage("Private Key"),
|
||||
"pwd": MessageLookupByLibrary.simpleMessage("Password"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("Rename"),
|
||||
"result": MessageLookupByLibrary.simpleMessage("Result"),
|
||||
"run": MessageLookupByLibrary.simpleMessage("Run"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Save"),
|
||||
@@ -116,12 +158,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"serverTabUnkown":
|
||||
MessageLookupByLibrary.simpleMessage("Unknown state"),
|
||||
"setting": MessageLookupByLibrary.simpleMessage("Setting"),
|
||||
"sftpDlPrepare":
|
||||
MessageLookupByLibrary.simpleMessage("Preparing to connect..."),
|
||||
"sftpNoDownloadTask":
|
||||
MessageLookupByLibrary.simpleMessage("No download task."),
|
||||
"sftpSSHConnected":
|
||||
MessageLookupByLibrary.simpleMessage("SFTP Connected"),
|
||||
"snippet": MessageLookupByLibrary.simpleMessage("Snippet"),
|
||||
"spentTime": m7,
|
||||
"start": MessageLookupByLibrary.simpleMessage("Start"),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("Stop"),
|
||||
"sureToDeleteServer": m3,
|
||||
"sureDelete": m8,
|
||||
"sureToDeleteServer": m9,
|
||||
"ttl": MessageLookupByLibrary.simpleMessage("TTL"),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("unknown"),
|
||||
"unknownError": MessageLookupByLibrary.simpleMessage("Unknown error"),
|
||||
"unkownConvertMode":
|
||||
MessageLookupByLibrary.simpleMessage("Unknown convert mode"),
|
||||
"updateIntervalEqual0": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -131,9 +182,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"upsideDown": MessageLookupByLibrary.simpleMessage("Upside Down"),
|
||||
"urlOrJson": MessageLookupByLibrary.simpleMessage("URL or JSON"),
|
||||
"user": MessageLookupByLibrary.simpleMessage("User"),
|
||||
"versionHaveUpdate": m4,
|
||||
"versionUnknownUpdate": m5,
|
||||
"versionUpdated": m6,
|
||||
"versionHaveUpdate": m10,
|
||||
"versionUnknownUpdate": m11,
|
||||
"versionUpdated": m12,
|
||||
"willTakEeffectImmediately":
|
||||
MessageLookupByLibrary.simpleMessage("Will take effect immediately")
|
||||
};
|
||||
|
||||
@@ -23,61 +23,95 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m0(rainSunMeGithub) =>
|
||||
"\n感谢 ${rainSunMeGithub} 参与软件测试。\n\n保留所有权利。";
|
||||
|
||||
static String m1(code) => "请求失败, 状态码: ${code}";
|
||||
static String m1(fileName) => "下载 [${fileName}] 到本地?";
|
||||
|
||||
static String m2(myGithub) => "\n用❤️制作 by ${myGithub}";
|
||||
static String m2(runningCount, stoppedCount) =>
|
||||
"${runningCount}个正在运行, ${stoppedCount}个已停止";
|
||||
|
||||
static String m3(server) => "你确定要删除服务器 [${server}] 吗?";
|
||||
static String m3(count) => "${count}个容器正在运行";
|
||||
|
||||
static String m4(build) => "找到新版本:v1.0.${build}, 点击更新";
|
||||
static String m4(percent, size) => "${size} 的 ${percent}%";
|
||||
|
||||
static String m5(build) => "当前:v1.0.${build}";
|
||||
static String m5(code) => "请求失败, 状态码: ${code}";
|
||||
|
||||
static String m6(build) => "当前:v1.0.${build}, 已是最新版本";
|
||||
static String m6(myGithub) => "\n用❤️制作 by ${myGithub}";
|
||||
|
||||
static String m7(time) => "耗时: ${time} 秒";
|
||||
|
||||
static String m8(name) => "确定删除[${name}]?";
|
||||
|
||||
static String m9(server) => "你确定要删除服务器 [${server}] 吗?";
|
||||
|
||||
static String m10(build) => "找到新版本:v1.0.${build}, 点击更新";
|
||||
|
||||
static String m11(build) => "当前:v1.0.${build}";
|
||||
|
||||
static String m12(build) => "当前:v1.0.${build}, 已是最新版本";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"aboutThanks": m0,
|
||||
"addAServer": MessageLookupByLibrary.simpleMessage("添加服务器"),
|
||||
"addPrivateKey": MessageLookupByLibrary.simpleMessage("添加一个私钥"),
|
||||
"alreadyLastDir": MessageLookupByLibrary.simpleMessage("已经是最上层目录了"),
|
||||
"appPrimaryColor": MessageLookupByLibrary.simpleMessage("App主要色"),
|
||||
"attention": MessageLookupByLibrary.simpleMessage("注意"),
|
||||
"backDir": MessageLookupByLibrary.simpleMessage("返回上一级"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("取消"),
|
||||
"choose": MessageLookupByLibrary.simpleMessage("选择"),
|
||||
"chooseDestination": MessageLookupByLibrary.simpleMessage("选择目标"),
|
||||
"choosePrivateKey": MessageLookupByLibrary.simpleMessage("选择私钥"),
|
||||
"clear": MessageLookupByLibrary.simpleMessage("清除"),
|
||||
"close": MessageLookupByLibrary.simpleMessage("关闭"),
|
||||
"containerStatus": MessageLookupByLibrary.simpleMessage("容器状态"),
|
||||
"convert": MessageLookupByLibrary.simpleMessage("转换"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("复制到剪切板"),
|
||||
"copyPath": MessageLookupByLibrary.simpleMessage("复制路径"),
|
||||
"createFolder": MessageLookupByLibrary.simpleMessage("创建文件夹"),
|
||||
"currentMode": MessageLookupByLibrary.simpleMessage("当前模式"),
|
||||
"debug": MessageLookupByLibrary.simpleMessage("调试"),
|
||||
"decode": MessageLookupByLibrary.simpleMessage("解码"),
|
||||
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||
"dl2Local": m1,
|
||||
"dockerStatusRunningAndStoppedFmt": m2,
|
||||
"dockerStatusRunningFmt": m3,
|
||||
"dockerWaitConnection": MessageLookupByLibrary.simpleMessage("请等待连接建立"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"downloadFinished": MessageLookupByLibrary.simpleMessage("下载完成!"),
|
||||
"downloadStatus": m4,
|
||||
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
||||
"encode": MessageLookupByLibrary.simpleMessage("编码"),
|
||||
"error": MessageLookupByLibrary.simpleMessage("出错了"),
|
||||
"exampleName": MessageLookupByLibrary.simpleMessage("名称示例"),
|
||||
"export": MessageLookupByLibrary.simpleMessage("导出"),
|
||||
"fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage("这些输入框不能为空。"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("开始"),
|
||||
"goSftpDlPage": MessageLookupByLibrary.simpleMessage("前往下载页?"),
|
||||
"host": MessageLookupByLibrary.simpleMessage("主机"),
|
||||
"httpFailedWithCode": m1,
|
||||
"httpFailedWithCode": m5,
|
||||
"import": MessageLookupByLibrary.simpleMessage("导入"),
|
||||
"importAndExport": MessageLookupByLibrary.simpleMessage("导入或导出"),
|
||||
"install": MessageLookupByLibrary.simpleMessage("安装"),
|
||||
"installDockerWithUrl": MessageLookupByLibrary.simpleMessage(
|
||||
"请先 https://docs.docker.com/engine/install docker"),
|
||||
"keepForeground": MessageLookupByLibrary.simpleMessage("请保持应用处于前台!"),
|
||||
"keyAuth": MessageLookupByLibrary.simpleMessage("公钥认证"),
|
||||
"launchPage": MessageLookupByLibrary.simpleMessage("启动页"),
|
||||
"license": MessageLookupByLibrary.simpleMessage("开源证书"),
|
||||
"loadingFiles": MessageLookupByLibrary.simpleMessage("正在加载目录。。。"),
|
||||
"loss": MessageLookupByLibrary.simpleMessage("丢包率"),
|
||||
"madeWithLove": m2,
|
||||
"madeWithLove": m6,
|
||||
"max": MessageLookupByLibrary.simpleMessage("最大"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
||||
"ms": MessageLookupByLibrary.simpleMessage("毫秒"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("名称"),
|
||||
"noClient": MessageLookupByLibrary.simpleMessage("没有SSH连接"),
|
||||
"noResult": MessageLookupByLibrary.simpleMessage("无结果"),
|
||||
"noSavedPrivateKey": MessageLookupByLibrary.simpleMessage("没有已保存的私钥。"),
|
||||
"noSavedSnippet": MessageLookupByLibrary.simpleMessage("没有已保存的代码片段。"),
|
||||
"noServerAvailable": MessageLookupByLibrary.simpleMessage("没有可用的服务器。"),
|
||||
"ok": MessageLookupByLibrary.simpleMessage("好"),
|
||||
"open": MessageLookupByLibrary.simpleMessage("打开"),
|
||||
"ping": MessageLookupByLibrary.simpleMessage("Ping"),
|
||||
"pingAvg": MessageLookupByLibrary.simpleMessage("平均:"),
|
||||
"pingInputIP": MessageLookupByLibrary.simpleMessage("请输入目标IP或域名"),
|
||||
@@ -87,6 +121,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"port": MessageLookupByLibrary.simpleMessage("端口"),
|
||||
"privateKey": MessageLookupByLibrary.simpleMessage("私钥"),
|
||||
"pwd": MessageLookupByLibrary.simpleMessage("密码"),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("重命名"),
|
||||
"result": MessageLookupByLibrary.simpleMessage("结果"),
|
||||
"run": MessageLookupByLibrary.simpleMessage("运行"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
@@ -100,12 +135,19 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"serverTabPlzSave": MessageLookupByLibrary.simpleMessage("请再次保存该私钥"),
|
||||
"serverTabUnkown": MessageLookupByLibrary.simpleMessage("未知状态"),
|
||||
"setting": MessageLookupByLibrary.simpleMessage("设置"),
|
||||
"sftpDlPrepare": MessageLookupByLibrary.simpleMessage("准备连接至服务器..."),
|
||||
"sftpNoDownloadTask": MessageLookupByLibrary.simpleMessage("没有下载任务"),
|
||||
"sftpSSHConnected":
|
||||
MessageLookupByLibrary.simpleMessage("SFTP 已连接,即将开始下载..."),
|
||||
"snippet": MessageLookupByLibrary.simpleMessage("代码片段"),
|
||||
"spentTime": m7,
|
||||
"start": MessageLookupByLibrary.simpleMessage("开始"),
|
||||
"stop": MessageLookupByLibrary.simpleMessage("停止"),
|
||||
"sureToDeleteServer": m3,
|
||||
"sureDelete": m8,
|
||||
"sureToDeleteServer": m9,
|
||||
"ttl": MessageLookupByLibrary.simpleMessage("缓存时间"),
|
||||
"unknown": MessageLookupByLibrary.simpleMessage("未知"),
|
||||
"unknownError": MessageLookupByLibrary.simpleMessage("未知错误"),
|
||||
"unkownConvertMode": MessageLookupByLibrary.simpleMessage("未知转换模式"),
|
||||
"updateIntervalEqual0": MessageLookupByLibrary.simpleMessage(
|
||||
"你设置为0,服务器状态不会自动刷新。\n你可以手动下拉刷新。"),
|
||||
@@ -114,9 +156,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"upsideDown": MessageLookupByLibrary.simpleMessage("上下交换"),
|
||||
"urlOrJson": MessageLookupByLibrary.simpleMessage("链接或JSON"),
|
||||
"user": MessageLookupByLibrary.simpleMessage("用户"),
|
||||
"versionHaveUpdate": m4,
|
||||
"versionUnknownUpdate": m5,
|
||||
"versionUpdated": m6,
|
||||
"versionHaveUpdate": m10,
|
||||
"versionUnknownUpdate": m11,
|
||||
"versionUpdated": m12,
|
||||
"willTakEeffectImmediately":
|
||||
MessageLookupByLibrary.simpleMessage("更改将会立即生效")
|
||||
};
|
||||
|
||||
@@ -829,6 +829,277 @@ class S {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Download`
|
||||
String get download {
|
||||
return Intl.message(
|
||||
'Download',
|
||||
name: 'download',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Copy path`
|
||||
String get copyPath {
|
||||
return Intl.message(
|
||||
'Copy path',
|
||||
name: 'copyPath',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Keep app foreground!`
|
||||
String get keepForeground {
|
||||
return Intl.message(
|
||||
'Keep app foreground!',
|
||||
name: 'keepForeground',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Download finished`
|
||||
String get downloadFinished {
|
||||
return Intl.message(
|
||||
'Download finished',
|
||||
name: 'downloadFinished',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `{percent}% of {size}`
|
||||
String downloadStatus(Object percent, Object size) {
|
||||
return Intl.message(
|
||||
'$percent% of $size',
|
||||
name: 'downloadStatus',
|
||||
desc: '',
|
||||
args: [percent, size],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Preparing to connect...`
|
||||
String get sftpDlPrepare {
|
||||
return Intl.message(
|
||||
'Preparing to connect...',
|
||||
name: 'sftpDlPrepare',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `SFTP Connected`
|
||||
String get sftpSSHConnected {
|
||||
return Intl.message(
|
||||
'SFTP Connected',
|
||||
name: 'sftpSSHConnected',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Spent time: {time} seconds`
|
||||
String spentTime(Object time) {
|
||||
return Intl.message(
|
||||
'Spent time: $time seconds',
|
||||
name: 'spentTime',
|
||||
desc: '',
|
||||
args: [time],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Back`
|
||||
String get backDir {
|
||||
return Intl.message(
|
||||
'Back',
|
||||
name: 'backDir',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Already in last directory.`
|
||||
String get alreadyLastDir {
|
||||
return Intl.message(
|
||||
'Already in last directory.',
|
||||
name: 'alreadyLastDir',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Open`
|
||||
String get open {
|
||||
return Intl.message(
|
||||
'Open',
|
||||
name: 'open',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Are you sure to delete [{name}]?`
|
||||
String sureDelete(Object name) {
|
||||
return Intl.message(
|
||||
'Are you sure to delete [$name]?',
|
||||
name: 'sureDelete',
|
||||
desc: '',
|
||||
args: [name],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Container status`
|
||||
String get containerStatus {
|
||||
return Intl.message(
|
||||
'Container status',
|
||||
name: 'containerStatus',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `No client`
|
||||
String get noClient {
|
||||
return Intl.message(
|
||||
'No client',
|
||||
name: 'noClient',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Please https://docs.docker.com/engine/install docker first.`
|
||||
String get installDockerWithUrl {
|
||||
return Intl.message(
|
||||
'Please https://docs.docker.com/engine/install docker first.',
|
||||
name: 'installDockerWithUrl',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Please wait for the connection to be established.`
|
||||
String get dockerWaitConnection {
|
||||
return Intl.message(
|
||||
'Please wait for the connection to be established.',
|
||||
name: 'dockerWaitConnection',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Unknown error`
|
||||
String get unknownError {
|
||||
return Intl.message(
|
||||
'Unknown error',
|
||||
name: 'unknownError',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `{count} container running.`
|
||||
String dockerStatusRunningFmt(Object count) {
|
||||
return Intl.message(
|
||||
'$count container running.',
|
||||
name: 'dockerStatusRunningFmt',
|
||||
desc: '',
|
||||
args: [count],
|
||||
);
|
||||
}
|
||||
|
||||
/// `{runningCount} running, {stoppedCount} container stopped.`
|
||||
String dockerStatusRunningAndStoppedFmt(
|
||||
Object runningCount, Object stoppedCount) {
|
||||
return Intl.message(
|
||||
'$runningCount running, $stoppedCount container stopped.',
|
||||
name: 'dockerStatusRunningAndStoppedFmt',
|
||||
desc: '',
|
||||
args: [runningCount, stoppedCount],
|
||||
);
|
||||
}
|
||||
|
||||
/// `install`
|
||||
String get install {
|
||||
return Intl.message(
|
||||
'install',
|
||||
name: 'install',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Loading files...`
|
||||
String get loadingFiles {
|
||||
return Intl.message(
|
||||
'Loading files...',
|
||||
name: 'loadingFiles',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `No download task.`
|
||||
String get sftpNoDownloadTask {
|
||||
return Intl.message(
|
||||
'No download task.',
|
||||
name: 'sftpNoDownloadTask',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Go to SFTP download page?`
|
||||
String get goSftpDlPage {
|
||||
return Intl.message(
|
||||
'Go to SFTP download page?',
|
||||
name: 'goSftpDlPage',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Create folder`
|
||||
String get createFolder {
|
||||
return Intl.message(
|
||||
'Create folder',
|
||||
name: 'createFolder',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Rename`
|
||||
String get rename {
|
||||
return Intl.message(
|
||||
'Rename',
|
||||
name: 'rename',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Download [{fileName}] to local?`
|
||||
String dl2Local(Object fileName) {
|
||||
return Intl.message(
|
||||
'Download [$fileName] to local?',
|
||||
name: 'dl2Local',
|
||||
desc: '',
|
||||
args: [fileName],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Error`
|
||||
String get error {
|
||||
return Intl.message(
|
||||
'Error',
|
||||
name: 'error',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||
|
||||
@@ -76,5 +76,32 @@
|
||||
"plzEnterPwd": "Please enter password.",
|
||||
"plzSelectKey": "Please select a key.",
|
||||
"exampleName": "Example name",
|
||||
"stop": "Stop"
|
||||
"stop": "Stop",
|
||||
"download": "Download",
|
||||
"copyPath": "Copy path",
|
||||
"keepForeground": "Keep app foreground!",
|
||||
"downloadFinished": "Download finished",
|
||||
"downloadStatus": "{percent}% of {size}",
|
||||
"sftpDlPrepare": "Preparing to connect...",
|
||||
"sftpSSHConnected": "SFTP Connected",
|
||||
"spentTime": "Spent time: {time} seconds",
|
||||
"backDir": "Back",
|
||||
"alreadyLastDir": "Already in last directory.",
|
||||
"open": "Open",
|
||||
"sureDelete": "Are you sure to delete [{name}]?",
|
||||
"containerStatus": "Container status",
|
||||
"noClient": "No client",
|
||||
"installDockerWithUrl": "Please https://docs.docker.com/engine/install docker first.",
|
||||
"dockerWaitConnection": "Please wait for the connection to be established.",
|
||||
"unknownError": "Unknown error",
|
||||
"dockerStatusRunningFmt": "{count} container running.",
|
||||
"dockerStatusRunningAndStoppedFmt": "{runningCount} running, {stoppedCount} container stopped.",
|
||||
"install": "install",
|
||||
"loadingFiles": "Loading files...",
|
||||
"sftpNoDownloadTask": "No download task.",
|
||||
"goSftpDlPage": "Go to SFTP download page?",
|
||||
"createFolder": "Create folder",
|
||||
"rename": "Rename",
|
||||
"dl2Local": "Download [{fileName}] to local?",
|
||||
"error": "Error"
|
||||
}
|
||||
@@ -76,5 +76,32 @@
|
||||
"plzEnterPwd": "请输入密码",
|
||||
"plzSelectKey": "请选择私钥",
|
||||
"exampleName": "名称示例",
|
||||
"stop": "停止"
|
||||
"stop": "停止",
|
||||
"download": "下载",
|
||||
"copyPath": "复制路径",
|
||||
"keepForeground": "请保持应用处于前台!",
|
||||
"downloadFinished": "下载完成!",
|
||||
"downloadStatus": "{size} 的 {percent}%",
|
||||
"sftpDlPrepare": "准备连接至服务器...",
|
||||
"sftpSSHConnected": "SFTP 已连接,即将开始下载...",
|
||||
"spentTime": "耗时: {time} 秒",
|
||||
"backDir": "返回上一级",
|
||||
"alreadyLastDir": "已经是最上层目录了",
|
||||
"open": "打开",
|
||||
"sureDelete": "确定删除[{name}]?",
|
||||
"containerStatus": "容器状态",
|
||||
"noClient": "没有SSH连接",
|
||||
"installDockerWithUrl": "请先 https://docs.docker.com/engine/install docker",
|
||||
"dockerWaitConnection": "请等待连接建立",
|
||||
"unknownError": "未知错误",
|
||||
"dockerStatusRunningFmt": "{count}个容器正在运行",
|
||||
"dockerStatusRunningAndStoppedFmt": "{runningCount}个正在运行, {stoppedCount}个已停止",
|
||||
"install": "安装",
|
||||
"loadingFiles": "正在加载目录。。。",
|
||||
"sftpNoDownloadTask": "没有下载任务",
|
||||
"goSftpDlPage": "前往下载页?",
|
||||
"createFolder": "创建文件夹",
|
||||
"rename": "重命名",
|
||||
"dl2Local": "下载 [{fileName}] 到本地?",
|
||||
"error": "出错了"
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:toolbox/data/provider/debug.dart';
|
||||
import 'package:toolbox/data/provider/docker.dart';
|
||||
import 'package:toolbox/data/provider/private_key.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/data/provider/sftp_download.dart';
|
||||
import 'package:toolbox/data/provider/snippet.dart';
|
||||
import 'package:toolbox/data/service/app.dart';
|
||||
import 'package:toolbox/data/store/private_key.dart';
|
||||
@@ -26,6 +27,7 @@ void setupLocatorForProviders() {
|
||||
locator.registerSingleton(ServerProvider());
|
||||
locator.registerSingleton(SnippetProvider());
|
||||
locator.registerSingleton(PrivateKeyProvider());
|
||||
locator.registerSingleton(SftpDownloadProvider());
|
||||
}
|
||||
|
||||
Future<void> setupLocatorForStores() async {
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:toolbox/data/provider/debug.dart';
|
||||
import 'package:toolbox/data/provider/docker.dart';
|
||||
import 'package:toolbox/data/provider/private_key.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/data/provider/sftp_download.dart';
|
||||
import 'package:toolbox/data/provider/snippet.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
|
||||
@@ -70,6 +71,8 @@ Future<void> main() async {
|
||||
ChangeNotifierProvider(create: (_) => locator<ServerProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<SnippetProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<PrivateKeyProvider>()),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => locator<SftpDownloadProvider>()),
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:toolbox/data/model/docker/ps.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/provider/docker.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/generated/l10n.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
import 'package:toolbox/view/widget/center_loading.dart';
|
||||
import 'package:toolbox/view/widget/two_line_text.dart';
|
||||
@@ -25,6 +26,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
final _docker = locator<DockerProvider>();
|
||||
final greyTextStyle = const TextStyle(color: Colors.grey);
|
||||
late MediaQueryData _media;
|
||||
late S s;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -36,6 +38,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_media = MediaQuery.of(context);
|
||||
s = S.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -46,7 +49,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
.firstWhere((element) => element.info == widget.spi)
|
||||
.client;
|
||||
if (client == null) {
|
||||
showSnackBar(context, const Text('No client found'));
|
||||
showSnackBar(context, Text(s.noClient));
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
@@ -87,7 +90,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
padding: const EdgeInsets.all(7),
|
||||
children: [
|
||||
_buildVersion(
|
||||
docker.edition ?? 'Unknown', docker.version ?? 'Unknown'),
|
||||
docker.edition ?? s.unknown, docker.version ?? s.unknown),
|
||||
_buildPsItems(running, docker)
|
||||
].map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
@@ -97,14 +100,14 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
Widget _buildSolution(String err) {
|
||||
switch (err) {
|
||||
case 'docker not found':
|
||||
return const UrlText(
|
||||
text: 'Please https://docs.docker.com/engine/install docker first.',
|
||||
replace: 'install',
|
||||
return UrlText(
|
||||
text: s.installDockerWithUrl,
|
||||
replace: s.install,
|
||||
);
|
||||
case 'no client':
|
||||
return const Text('Plz wait for the connection to be established.');
|
||||
return Text(s.dockerWaitConnection);
|
||||
default:
|
||||
return const Text('Unknown error');
|
||||
return Text(s.unknownError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +123,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
|
||||
Widget _buildPsItems(List<DockerPsItem> running, DockerProvider docker) {
|
||||
return ExpansionTile(
|
||||
title: const Text('Container Status'),
|
||||
title: Text(s.containerStatus),
|
||||
subtitle: Text(_buildSubtitle(running), style: greyTextStyle),
|
||||
children: running.map((item) {
|
||||
return ListTile(
|
||||
@@ -186,8 +189,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
final runningCount = running.where((element) => element.running).length;
|
||||
final stoped = running.length - runningCount;
|
||||
if (stoped == 0) {
|
||||
return '$runningCount container running.';
|
||||
return s.dockerStatusRunningFmt(runningCount);
|
||||
}
|
||||
return '$runningCount running, $stoped stoped.';
|
||||
return s.dockerStatusRunningAndStoppedFmt(runningCount, stoped);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'package:toolbox/view/page/ping.dart';
|
||||
import 'package:toolbox/view/page/private_key/list.dart';
|
||||
import 'package:toolbox/view/page/server/tab.dart';
|
||||
import 'package:toolbox/view/page/setting.dart';
|
||||
import 'package:toolbox/view/page/sftp/downloaded.dart';
|
||||
import 'package:toolbox/view/page/snippet/list.dart';
|
||||
import 'package:toolbox/view/widget/url_text.dart';
|
||||
|
||||
@@ -233,6 +234,13 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
const StoredPrivateKeysPage(), 'private key list')
|
||||
.go(context),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.download),
|
||||
title: Text(s.download),
|
||||
onTap: () =>
|
||||
AppRoute(const SFTPDownloadedPage(), 'snippet list')
|
||||
.go(context),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.snippet_folder),
|
||||
title: Text(s.snippet),
|
||||
|
||||
@@ -21,7 +21,7 @@ import 'package:toolbox/view/page/apt.dart';
|
||||
import 'package:toolbox/view/page/docker.dart';
|
||||
import 'package:toolbox/view/page/server/detail.dart';
|
||||
import 'package:toolbox/view/page/server/edit.dart';
|
||||
import 'package:toolbox/view/page/sftp.dart';
|
||||
import 'package:toolbox/view/page/sftp/view.dart';
|
||||
import 'package:toolbox/view/page/snippet/list.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
|
||||
|
||||
187
lib/view/page/sftp/downloaded.dart
Normal file
187
lib/view/page/sftp/downloaded.dart
Normal file
@@ -0,0 +1,187 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:toolbox/core/extension/colorx.dart';
|
||||
import 'package:toolbox/core/extension/numx.dart';
|
||||
import 'package:toolbox/core/extension/stringx.dart';
|
||||
import 'package:toolbox/core/route.dart';
|
||||
import 'package:toolbox/core/utils.dart';
|
||||
import 'package:toolbox/data/model/app/path_with_prefix.dart';
|
||||
import 'package:toolbox/data/res/color.dart';
|
||||
import 'package:toolbox/data/res/font_style.dart';
|
||||
import 'package:toolbox/data/res/path.dart';
|
||||
import 'package:toolbox/generated/l10n.dart';
|
||||
import 'package:toolbox/view/page/sftp/downloading.dart';
|
||||
import 'package:toolbox/view/widget/fade_in.dart';
|
||||
|
||||
class SFTPDownloadedPage extends StatefulWidget {
|
||||
const SFTPDownloadedPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SFTPDownloadedPage> createState() => _SFTPDownloadedPageState();
|
||||
}
|
||||
|
||||
class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
|
||||
PathWithPrefix? _path;
|
||||
String? _prefixPath;
|
||||
late S s;
|
||||
late ThemeData _theme;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
sftpDownloadDir.then((dir) {
|
||||
_path = PathWithPrefix(dir.path);
|
||||
_prefixPath = dir.path + '/';
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
s = S.of(context);
|
||||
_theme = Theme.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(s.download),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.downloading),
|
||||
onPressed: () =>
|
||||
AppRoute(const SFTPDownloadingPage(), 'sftp downloading')
|
||||
.go(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: FadeIn(
|
||||
child: _buildBody(),
|
||||
key: UniqueKey(),
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: _buildPath(),
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: (() {
|
||||
if (_path!.path == _prefixPath) {
|
||||
showSnackBar(context, Text(s.alreadyLastDir));
|
||||
return;
|
||||
}
|
||||
_path!.update('..');
|
||||
setState(() {});
|
||||
}),
|
||||
child: const Icon(Icons.keyboard_arrow_left),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPath() {
|
||||
return Container(
|
||||
color: _theme.appBarTheme.foregroundColor,
|
||||
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Divider(),
|
||||
(_path?.path ?? s.loadingFiles).omitStartStr(
|
||||
style: TextStyle(
|
||||
color:
|
||||
primaryColor.isBrightColor ? Colors.black : Colors.white),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (_path == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
final dir = Directory(_path!.path);
|
||||
final files = dir.listSync();
|
||||
return ListView.builder(
|
||||
itemCount: files.length,
|
||||
itemBuilder: (context, index) {
|
||||
var file = files[index];
|
||||
var fileName = file.path.split('/').last;
|
||||
var stat = file.statSync();
|
||||
var isDir = stat.type == FileSystemEntityType.directory;
|
||||
|
||||
return ListTile(
|
||||
leading: isDir
|
||||
? const Icon(Icons.folder)
|
||||
: const Icon(Icons.insert_drive_file),
|
||||
title: Text(fileName),
|
||||
subtitle: isDir ? null : Text(stat.size.convertBytes),
|
||||
trailing: Text(
|
||||
stat.modified
|
||||
.toString()
|
||||
.substring(0, stat.modified.toString().length - 4),
|
||||
style: grey,
|
||||
),
|
||||
onTap: () {
|
||||
if (!isDir) {
|
||||
showFileActionDialog(file);
|
||||
return;
|
||||
}
|
||||
_path!.update(fileName);
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void showFileActionDialog(FileSystemEntity file) {
|
||||
final fileName = file.path.split('/').last;
|
||||
showRoundDialog(
|
||||
context,
|
||||
s.choose,
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(s.delete),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
showRoundDialog(
|
||||
context, s.sureDelete(fileName), const SizedBox(), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(s.cancel)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
file.deleteSync();
|
||||
setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(s.ok),
|
||||
),
|
||||
]);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.open_in_new),
|
||||
title: Text(s.open),
|
||||
onTap: () {
|
||||
Share.shareFiles([file.absolute.path],
|
||||
text: '$fileName from ServerBox');
|
||||
}),
|
||||
],
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: (() => Navigator.of(context).pop()),
|
||||
child: Text(s.close))
|
||||
]);
|
||||
}
|
||||
}
|
||||
105
lib/view/page/sftp/downloading.dart
Normal file
105
lib/view/page/sftp/downloading.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:toolbox/core/extension/numx.dart';
|
||||
import 'package:toolbox/core/utils.dart';
|
||||
import 'package:toolbox/data/model/sftp/download_status.dart';
|
||||
import 'package:toolbox/data/provider/sftp_download.dart';
|
||||
import 'package:toolbox/data/res/font_style.dart';
|
||||
import 'package:toolbox/generated/l10n.dart';
|
||||
import 'package:toolbox/view/widget/center_loading.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
|
||||
class SFTPDownloadingPage extends StatefulWidget {
|
||||
const SFTPDownloadingPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SFTPDownloadingPageState createState() => _SFTPDownloadingPageState();
|
||||
}
|
||||
|
||||
class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
|
||||
late S s;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
s = S.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
s.download,
|
||||
style: size18,
|
||||
),
|
||||
),
|
||||
body: _buildBody(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return Consumer<SftpDownloadProvider>(builder: (__, pro, _) {
|
||||
if (pro.status.isEmpty) {
|
||||
return Center(
|
||||
child: Text(s.sftpNoDownloadTask),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(13),
|
||||
itemCount: pro.status.length,
|
||||
itemBuilder: (context, index) {
|
||||
final status = pro.status[index];
|
||||
return _buildItem(status);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _wrapInCard(SftpDownloadStatus status, String? subtitle,
|
||||
{Widget? trailing}) {
|
||||
return RoundRectCard(ListTile(
|
||||
title: Text(status.fileName),
|
||||
subtitle: subtitle == null
|
||||
? null
|
||||
: Text(
|
||||
subtitle,
|
||||
style: grey,
|
||||
),
|
||||
trailing: trailing,
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildItem(SftpDownloadStatus status) {
|
||||
if (status.error != null) {
|
||||
showSnackBar(context, Text(status.error.toString()));
|
||||
}
|
||||
switch (status.status) {
|
||||
case SftpWorkerStatus.finished:
|
||||
return _wrapInCard(status,
|
||||
'${s.downloadFinished}, ${s.spentTime(status.spentTime ?? s.unknown)}',
|
||||
trailing: IconButton(
|
||||
onPressed: () => Share.shareFiles([status.item.localPath],
|
||||
text: '${status.fileName} from ServerBox'),
|
||||
icon: const Icon(Icons.open_in_new)));
|
||||
case SftpWorkerStatus.downloading:
|
||||
return _wrapInCard(
|
||||
status,
|
||||
s.downloadStatus((status.progress ?? 0.0).toStringAsFixed(2),
|
||||
(status.size ?? 0).convertBytes),
|
||||
trailing:
|
||||
CircularProgressIndicator(value: (status.progress ?? 0) / 100));
|
||||
case SftpWorkerStatus.preparing:
|
||||
return _wrapInCard(status, s.sftpDlPrepare, trailing: centerLoading);
|
||||
case SftpWorkerStatus.sshConnectted:
|
||||
return _wrapInCard(status, s.sftpSSHConnected, trailing: centerLoading);
|
||||
default:
|
||||
return _wrapInCard(status, s.unknown,
|
||||
trailing: const Icon(
|
||||
Icons.error,
|
||||
size: 40,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/extension/numx.dart';
|
||||
import 'package:toolbox/core/extension/stringx.dart';
|
||||
import 'package:toolbox/core/route.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/model/sftp/absolute_path.dart';
|
||||
import 'package:toolbox/data/model/sftp/sftp_side_status.dart';
|
||||
import 'package:toolbox/data/model/sftp/download_worker.dart';
|
||||
import 'package:toolbox/data/model/sftp/browser_status.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/data/provider/sftp_download.dart';
|
||||
import 'package:toolbox/data/res/path.dart';
|
||||
import 'package:toolbox/data/store/private_key.dart';
|
||||
import 'package:toolbox/generated/l10n.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
import 'package:toolbox/view/page/sftp/downloading.dart';
|
||||
import 'package:toolbox/view/widget/fade_in.dart';
|
||||
import 'package:toolbox/view/widget/two_line_text.dart';
|
||||
|
||||
@@ -20,16 +28,18 @@ class SFTPPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SFTPPageState extends State<SFTPPage> {
|
||||
final SFTPSideViewStatus _status = SFTPSideViewStatus();
|
||||
final SftpBrowserStatus _status = SftpBrowserStatus();
|
||||
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
late MediaQueryData _media;
|
||||
late S s;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_media = MediaQuery.of(context);
|
||||
s = S.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -133,94 +143,66 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete),
|
||||
title: const Text('Delete'),
|
||||
title: Text(s.delete),
|
||||
onTap: () => delete(context, file),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.folder),
|
||||
title: const Text('Create Folder'),
|
||||
title: Text(s.createFolder),
|
||||
onTap: () => mkdir(context)),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.edit),
|
||||
title: const Text('Rename'),
|
||||
title: Text(s.rename),
|
||||
onTap: () => rename(context, file),
|
||||
),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.download),
|
||||
// title: const Text('Download'),
|
||||
// onTap: () => download(context, file),
|
||||
// )
|
||||
ListTile(
|
||||
leading: const Icon(Icons.download),
|
||||
title: Text(s.download),
|
||||
onTap: () => download(context, file),
|
||||
)
|
||||
],
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'))
|
||||
child: Text(s.cancel))
|
||||
]);
|
||||
}
|
||||
|
||||
// void download(BuildContext context, SftpName name) {
|
||||
// showRoundDialog(
|
||||
// context, 'Download', Text('Download ${name.filename} to local?'), [
|
||||
// TextButton(
|
||||
// onPressed: () => Navigator.of(context).pop(),
|
||||
// child: const Text('Cancel')),
|
||||
// TextButton(
|
||||
// onPressed: () async {
|
||||
// var result = '';
|
||||
// try {
|
||||
// Navigator.of(context).pop();
|
||||
// showRoundDialog(
|
||||
// context,
|
||||
// name.filename,
|
||||
// const Text('Downloading...\nKepp this app in the foreground.',
|
||||
// textAlign: TextAlign.center),
|
||||
// [],
|
||||
// barrierDismiss: false);
|
||||
// final path = await sftpDownloadDir;
|
||||
// final local = File('${path.path}/${name.filename}');
|
||||
// if (await local.exists()) {
|
||||
// await local.delete();
|
||||
// }
|
||||
|
||||
// final localFile =
|
||||
// await local.open(mode: FileMode.writeOnlyAppend);
|
||||
// final remotePath = _status.path!.path + '/' + name.filename;
|
||||
// final file = await _status.client!.open(remotePath);
|
||||
// final size = (await file.stat()).size;
|
||||
// if (size == null) {
|
||||
// throw Exception('can not get file size');
|
||||
// }
|
||||
|
||||
// const chunkSize = 1024 * 128;
|
||||
// for (var i = 0; i < size; i += chunkSize) {
|
||||
// final data = file.read(length: chunkSize);
|
||||
// await for (var item in data) {
|
||||
// localFile.writeFrom(item);
|
||||
// }
|
||||
// }
|
||||
// } catch (e) {
|
||||
// result = e.toString();
|
||||
// } finally {
|
||||
// Navigator.of(context).pop();
|
||||
// if (result.isEmpty) {
|
||||
// result = 'Donwloaded successfully.';
|
||||
// }
|
||||
// showRoundDialog(context, 'Result', Text(result), [
|
||||
// TextButton(
|
||||
// onPressed: () => Navigator.of(context).pop(),
|
||||
// child: const Text('OK'))
|
||||
// ]);
|
||||
// }
|
||||
// },
|
||||
// child: const Text('Download'))
|
||||
// ]);
|
||||
// }
|
||||
void download(BuildContext context, SftpName name) {
|
||||
showRoundDialog(
|
||||
context, s.download, Text(s.dl2Local(name.filename)), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(s.cancel)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
final prePath = _status.path!.path;
|
||||
final remotePath =
|
||||
prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
|
||||
final local = '${(await sftpDownloadDir).path}$remotePath';
|
||||
final pubKeyId = _status.spi!.pubKeyId;
|
||||
locator<SftpDownloadProvider>().add(
|
||||
DownloadItem(_status.spi!, remotePath, local),
|
||||
key: pubKeyId == null
|
||||
? null
|
||||
: locator<PrivateKeyStore>().get(pubKeyId).privateKey);
|
||||
showRoundDialog(context, s.goSftpDlPage, const SizedBox(), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(s.cancel)),
|
||||
TextButton(onPressed: () => AppRoute(const SFTPDownloadingPage(), 'sftp downloading'), child: Text(s.ok))
|
||||
]);
|
||||
},
|
||||
child: Text(s.download))
|
||||
]);
|
||||
}
|
||||
|
||||
void delete(BuildContext context, SftpName file) {
|
||||
Navigator.of(context).pop();
|
||||
showRoundDialog(
|
||||
context, 'Confirm', Text('Are you sure to delete ${file.filename}?'), [
|
||||
context, s.attention, Text(s.sureDelete(file.filename)), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel')),
|
||||
@@ -230,9 +212,9 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Navigator.of(context).pop();
|
||||
listDir();
|
||||
},
|
||||
child: const Text(
|
||||
'Delete',
|
||||
style: TextStyle(color: Colors.red),
|
||||
child: Text(
|
||||
s.delete,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
)),
|
||||
]);
|
||||
}
|
||||
@@ -242,25 +224,25 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
final textController = TextEditingController();
|
||||
showRoundDialog(
|
||||
context,
|
||||
'Create Folder',
|
||||
s.createFolder,
|
||||
TextField(
|
||||
controller: textController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Folder Name',
|
||||
decoration: InputDecoration(
|
||||
labelText: s.name,
|
||||
),
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel')),
|
||||
child: Text(s.cancel)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (textController.text == '') {
|
||||
showRoundDialog(context, 'Attention',
|
||||
const Text('You need input a name.'), [
|
||||
showRoundDialog(context, s.attention,
|
||||
Text(s.fieldMustNotEmpty), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK')),
|
||||
child: Text(s.ok)),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
@@ -269,9 +251,9 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Navigator.of(context).pop();
|
||||
listDir();
|
||||
},
|
||||
child: const Text(
|
||||
'Create',
|
||||
style: TextStyle(color: Colors.red),
|
||||
child: Text(
|
||||
s.ok,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
)),
|
||||
]);
|
||||
}
|
||||
@@ -281,25 +263,25 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
final textController = TextEditingController();
|
||||
showRoundDialog(
|
||||
context,
|
||||
'Rename',
|
||||
s.rename,
|
||||
TextField(
|
||||
controller: textController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'New Name',
|
||||
decoration: InputDecoration(
|
||||
labelText: s.name,
|
||||
),
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel')),
|
||||
child: Text(s.cancel)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (textController.text == '') {
|
||||
showRoundDialog(context, 'Attention',
|
||||
const Text('You need input a name.'), [
|
||||
showRoundDialog(context, s.attention,
|
||||
Text(s.fieldMustNotEmpty), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK')),
|
||||
child: Text(s.ok)),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
@@ -308,9 +290,9 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Navigator.of(context).pop();
|
||||
listDir();
|
||||
},
|
||||
child: const Text(
|
||||
'Rename',
|
||||
style: TextStyle(color: Colors.red),
|
||||
child: Text(
|
||||
s.rename,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
)),
|
||||
]);
|
||||
}
|
||||
@@ -336,10 +318,10 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
await showRoundDialog(context, 'Error', Text(e.toString()), [
|
||||
await showRoundDialog(context, s.error, Text(e.toString()), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('OK'))
|
||||
child: Text(s.ok))
|
||||
]);
|
||||
if (_status.path!.undo()) {
|
||||
await listDir();
|
||||
@@ -350,43 +332,9 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Widget _buildDestSelector() {
|
||||
final str = _status.path?.path;
|
||||
return ExpansionTile(
|
||||
title: Text(_status.spi?.name ?? 'Choose target'),
|
||||
title: Text(_status.spi?.name ?? s.chooseDestination),
|
||||
subtitle: _status.selected
|
||||
? LayoutBuilder(builder: (context, size) {
|
||||
bool exceeded = false;
|
||||
int len = 0;
|
||||
for (; !exceeded && len < str!.length; len++) {
|
||||
// Build the textspan
|
||||
var span = TextSpan(
|
||||
text: '...' + str.substring(str.length - len),
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.bodyText1?.fontSize ??
|
||||
14),
|
||||
);
|
||||
|
||||
// Use a textpainter to determine if it will exceed max lines
|
||||
var tp = TextPainter(
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.left,
|
||||
textDirection: TextDirection.ltr,
|
||||
text: span,
|
||||
);
|
||||
|
||||
// trigger it to layout
|
||||
tp.layout(maxWidth: size.maxWidth);
|
||||
|
||||
// whether the text overflowed or not
|
||||
exceeded = tp.didExceedMaxLines;
|
||||
}
|
||||
|
||||
return Text(
|
||||
(exceeded ? '...' : '') + str!.substring(str.length - len),
|
||||
overflow: TextOverflow.clip,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
);
|
||||
})
|
||||
? str!.omitStartStr(style: const TextStyle(color: Colors.grey))
|
||||
: null,
|
||||
children: locator<ServerProvider>()
|
||||
.servers
|
||||
@@ -29,8 +29,8 @@ class _MyFadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -6,9 +6,11 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import path_provider_macos
|
||||
import share_plus_macos
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
- path_provider_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- share_plus_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
|
||||
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -15,12 +18,15 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral
|
||||
path_provider_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
|
||||
share_plus_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||
path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f
|
||||
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
|
||||
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
|
||||
|
||||
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
|
||||
|
||||
@@ -73,8 +73,8 @@ Future<void> updateBuildData() async {
|
||||
await writeStaicConfigFile(data, 'BuildData', buildDataFilePath);
|
||||
}
|
||||
|
||||
void dartFormat() {
|
||||
final result = Process.runSync('dart', ['format', '.']);
|
||||
Future<void> dartFormat() async {
|
||||
final result = await Process.run('dart', ['format', '.']);
|
||||
print('\n' + result.stdout);
|
||||
if (result.exitCode != 0) {
|
||||
print(result.stderr);
|
||||
@@ -83,7 +83,7 @@ void dartFormat() {
|
||||
}
|
||||
|
||||
void flutterRun(String? mode) {
|
||||
Process.start('flutter', ['run', mode == null ? '' : '--$mode'],
|
||||
Process.start('flutter', mode == null ? ['run'] : ['run', '--$mode'],
|
||||
mode: ProcessStartMode.inheritStdio, runInShell: true);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ void main(List<String> args) async {
|
||||
final buildFunc = [flutterBuildIOS, flutterBuildAndroid];
|
||||
build = await getGitCommitCount();
|
||||
await updateBuildData();
|
||||
dartFormat();
|
||||
await dartFormat();
|
||||
if (args.length > 1) {
|
||||
final platform = args[1];
|
||||
switch (platform) {
|
||||
|
||||
58
pubspec.lock
58
pubspec.lock
@@ -138,6 +138,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
easy_isolate:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: easy_isolate
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
extended_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -347,6 +354,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -362,7 +376,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
@@ -480,6 +494,48 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.8+1"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
share_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
share_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
share_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
share_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
||||
@@ -57,6 +57,9 @@ dependencies:
|
||||
marquee: ^2.2.0
|
||||
dropdown_button2: ^1.1.1
|
||||
flutter_advanced_drawer: ^1.3.0
|
||||
path_provider: ^2.0.9
|
||||
easy_isolate: ^1.3.0
|
||||
share_plus: ^4.0.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_native_splash: ^2.1.6
|
||||
|
||||
Reference in New Issue
Block a user