#33 new: upload file to sftp from file picker

This commit is contained in:
lollipopkit
2023-06-02 21:51:39 +08:00
parent f0bf95a7d2
commit 8c25b5e60b
24 changed files with 258 additions and 174 deletions

View File

@@ -360,12 +360,6 @@ abstract class S {
/// **'Download'** /// **'Download'**
String get download; String get download;
/// No description provided for @downloadFinished.
///
/// In en, this message translates to:
/// **'Download finished'**
String get downloadFinished;
/// No description provided for @downloadStatus. /// No description provided for @downloadStatus.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -462,6 +456,12 @@ abstract class S {
/// **'Files'** /// **'Files'**
String get files; String get files;
/// No description provided for @finished.
///
/// In en, this message translates to:
/// **'Finished'**
String get finished;
/// No description provided for @font. /// No description provided for @font.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -528,6 +528,12 @@ abstract class S {
/// **'Import'** /// **'Import'**
String get import; String get import;
/// No description provided for @inner.
///
/// In en, this message translates to:
/// **'Inner'**
String get inner;
/// No description provided for @inputDomainHere. /// No description provided for @inputDomainHere.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -666,6 +672,12 @@ abstract class S {
/// **'min'** /// **'min'**
String get min; String get min;
/// No description provided for @mission.
///
/// In en, this message translates to:
/// **'Mission'**
String get mission;
/// No description provided for @ms. /// No description provided for @ms.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1062,6 +1074,12 @@ abstract class S {
/// **'Are you sure to delete server [{server}]?'** /// **'Are you sure to delete server [{server}]?'**
String sureToDeleteServer(Object server); String sureToDeleteServer(Object server);
/// No description provided for @system.
///
/// In en, this message translates to:
/// **'System'**
String get system;
/// No description provided for @tag. /// No description provided for @tag.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -144,9 +144,6 @@ class SDe extends S {
@override @override
String get download => 'Download'; String get download => 'Download';
@override
String get downloadFinished => 'Download abgeschlossen';
@override @override
String downloadStatus(Object percent, Object size) { String downloadStatus(Object percent, Object size) {
return '$percent% von $size'; return '$percent% von $size';
@@ -201,6 +198,9 @@ class SDe extends S {
@override @override
String get files => 'Dateien'; String get files => 'Dateien';
@override
String get finished => 'fertiggestellt';
@override @override
String get font => 'Schriftarten'; String get font => 'Schriftarten';
@@ -238,6 +238,9 @@ class SDe extends S {
@override @override
String get import => 'Importieren'; String get import => 'Importieren';
@override
String get inner => 'Eingebaut';
@override @override
String get inputDomainHere => 'Domain eingeben'; String get inputDomainHere => 'Domain eingeben';
@@ -311,6 +314,9 @@ class SDe extends S {
@override @override
String get min => 'min'; String get min => 'min';
@override
String get mission => 'Mission';
@override @override
String get ms => 'ms'; String get ms => 'ms';
@@ -521,6 +527,9 @@ class SDe extends S {
return 'Bist du sicher, dass du [$server] löschen willst?'; return 'Bist du sicher, dass du [$server] löschen willst?';
} }
@override
String get system => 'Systeme';
@override @override
String get tag => 'Tags'; String get tag => 'Tags';

View File

@@ -144,9 +144,6 @@ class SEn extends S {
@override @override
String get download => 'Download'; String get download => 'Download';
@override
String get downloadFinished => 'Download finished';
@override @override
String downloadStatus(Object percent, Object size) { String downloadStatus(Object percent, Object size) {
return '$percent% of $size'; return '$percent% of $size';
@@ -201,6 +198,9 @@ class SEn extends S {
@override @override
String get files => 'Files'; String get files => 'Files';
@override
String get finished => 'Finished';
@override @override
String get font => 'Font'; String get font => 'Font';
@@ -238,6 +238,9 @@ class SEn extends S {
@override @override
String get import => 'Import'; String get import => 'Import';
@override
String get inner => 'Inner';
@override @override
String get inputDomainHere => 'Input Domain here'; String get inputDomainHere => 'Input Domain here';
@@ -311,6 +314,9 @@ class SEn extends S {
@override @override
String get min => 'min'; String get min => 'min';
@override
String get mission => 'Mission';
@override @override
String get ms => 'ms'; String get ms => 'ms';
@@ -521,6 +527,9 @@ class SEn extends S {
return 'Are you sure to delete server [$server]?'; return 'Are you sure to delete server [$server]?';
} }
@override
String get system => 'System';
@override @override
String get tag => 'Tags'; String get tag => 'Tags';

View File

@@ -144,9 +144,6 @@ class SZh extends S {
@override @override
String get download => '下载'; String get download => '下载';
@override
String get downloadFinished => '下载完成';
@override @override
String downloadStatus(Object percent, Object size) { String downloadStatus(Object percent, Object size) {
return '$size$percent%'; return '$size$percent%';
@@ -201,6 +198,9 @@ class SZh extends S {
@override @override
String get files => '文件'; String get files => '文件';
@override
String get finished => '已完成';
@override @override
String get font => '字体'; String get font => '字体';
@@ -238,6 +238,9 @@ class SZh extends S {
@override @override
String get import => '导入'; String get import => '导入';
@override
String get inner => '内置';
@override @override
String get inputDomainHere => '在这里输入域名'; String get inputDomainHere => '在这里输入域名';
@@ -311,6 +314,9 @@ class SZh extends S {
@override @override
String get min => '最小'; String get min => '最小';
@override
String get mission => '任务';
@override @override
String get ms => '毫秒'; String get ms => '毫秒';
@@ -521,6 +527,9 @@ class SZh extends S {
return '你确定要删除服务器 [$server] 吗?'; return '你确定要删除服务器 [$server] 吗?';
} }
@override
String get system => '系统';
@override @override
String get tag => '标签'; String get tag => '标签';
@@ -751,9 +760,6 @@ class SZhTw extends SZh {
@override @override
String get download => '下載'; String get download => '下載';
@override
String get downloadFinished => '下載完成';
@override @override
String downloadStatus(Object percent, Object size) { String downloadStatus(Object percent, Object size) {
return '$size$percent%'; return '$size$percent%';
@@ -808,6 +814,9 @@ class SZhTw extends SZh {
@override @override
String get files => '文件'; String get files => '文件';
@override
String get finished => '已完成';
@override @override
String get font => '字體'; String get font => '字體';
@@ -845,6 +854,9 @@ class SZhTw extends SZh {
@override @override
String get import => '導入'; String get import => '導入';
@override
String get inner => '內置';
@override @override
String get inputDomainHere => '在這裡輸入域名'; String get inputDomainHere => '在這裡輸入域名';
@@ -918,6 +930,9 @@ class SZhTw extends SZh {
@override @override
String get min => '最小'; String get min => '最小';
@override
String get mission => '任務';
@override @override
String get ms => '毫秒'; String get ms => '毫秒';
@@ -1128,6 +1143,9 @@ class SZhTw extends SZh {
return '你確定要刪除服務器 [$server] 嗎?'; return '你確定要刪除服務器 [$server] 嗎?';
} }
@override
String get system => '系統';
@override @override
String get tag => '标签'; String get tag => '标签';

View File

@@ -360,7 +360,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 341; CURRENT_PROJECT_VERSION = 343;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -368,7 +368,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.341; MARKETING_VERSION = 1.0.343;
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";
@@ -491,7 +491,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 341; CURRENT_PROJECT_VERSION = 343;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -499,7 +499,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.341; MARKETING_VERSION = 1.0.343;
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";
@@ -516,7 +516,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 341; CURRENT_PROJECT_VERSION = 343;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -524,7 +524,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.341; MARKETING_VERSION = 1.0.343;
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";

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import '../server/server_private_info.dart'; import '../server/server_private_info.dart';
import 'worker.dart'; import 'worker.dart';
@@ -24,6 +26,7 @@ class SftpReqStatus {
final SftpReqItem item; final SftpReqItem item;
final void Function() notifyListeners; final void Function() notifyListeners;
late SftpWorker worker; late SftpWorker worker;
final Completer? completer;
String get fileName => item.localPath.split('/').last; String get fileName => item.localPath.split('/').last;
@@ -38,12 +41,11 @@ class SftpReqStatus {
required this.item, required this.item,
required this.notifyListeners, required this.notifyListeners,
required SftpReqType type, required SftpReqType type,
String? key, this.completer,
}) : id = DateTime.now().microsecondsSinceEpoch { }) : id = DateTime.now().microsecondsSinceEpoch {
worker = SftpWorker( worker = SftpWorker(
onNotify: onNotify, onNotify: onNotify,
item: item, item: item,
privateKey: key,
type: type, type: type,
); );
worker.init(); worker.init();
@@ -61,6 +63,7 @@ class SftpReqStatus {
status = event; status = event;
if (status == SftpWorkerStatus.finished) { if (status == SftpWorkerStatus.finished) {
worker.dispose(); worker.dispose();
completer?.complete();
} }
break; break;
case double: case double:

View File

@@ -12,7 +12,6 @@ import 'req.dart';
class SftpWorker { class SftpWorker {
final Function(Object event) onNotify; final Function(Object event) onNotify;
final SftpReqItem item; final SftpReqItem item;
final String? privateKey;
final SftpReqType type; final SftpReqType type;
final worker = Worker(); final worker = Worker();
@@ -21,7 +20,6 @@ class SftpWorker {
required this.onNotify, required this.onNotify,
required this.item, required this.item,
required this.type, required this.type,
this.privateKey,
}); });
void dispose() { void dispose() {
@@ -37,7 +35,7 @@ class SftpWorker {
isolateMessageHandler, isolateMessageHandler,
errorHandler: print, errorHandler: print,
); );
worker.sendMessage(SftpReq(item: item, privateKey: privateKey, type: type)); worker.sendMessage(SftpReq(item: item, type: type));
} }
/// Handle the messages coming from the isolate /// Handle the messages coming from the isolate

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:toolbox/core/provider_base.dart'; import 'package:toolbox/core/provider_base.dart';
import '../model/sftp/req.dart'; import '../model/sftp/req.dart';
@@ -25,12 +27,12 @@ class SftpProvider extends ProviderBase {
return found.first; return found.first;
} }
void add(SftpReqItem item, SftpReqType type, {String? key}) { void add(SftpReqItem item, SftpReqType type, {Completer? completer}) {
_status.add(SftpReqStatus( _status.add(SftpReqStatus(
item: item, item: item,
notifyListeners: notifyListeners, notifyListeners: notifyListeners,
key: key,
type: type, type: type,
completer: completer,
)); ));
} }
} }

View File

@@ -2,8 +2,8 @@
class BuildData { class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 341; static const int build = 343;
static const String engine = "3.10.2"; static const String engine = "3.10.2";
static const String buildAt = "2023-05-31 19:23:57.263324"; static const String buildAt = "2023-06-01 16:27:31.059809";
static const int modifications = 6; static const int modifications = 4;
} }

View File

@@ -44,7 +44,6 @@
"dockerStatusRunningAndStoppedFmt": "{runningCount} aktiv, {stoppedCount} container gestoppt.", "dockerStatusRunningAndStoppedFmt": "{runningCount} aktiv, {stoppedCount} container gestoppt.",
"dockerStatusRunningFmt": "{count} Container aktiv", "dockerStatusRunningFmt": "{count} Container aktiv",
"download": "Download", "download": "Download",
"downloadFinished": "Download abgeschlossen",
"downloadStatus": "{percent}% von {size}", "downloadStatus": "{percent}% von {size}",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"editor": "Redakteure", "editor": "Redakteure",
@@ -61,6 +60,7 @@
"fileNotExist": "{file} existiert nicht", "fileNotExist": "{file} existiert nicht",
"fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}", "fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}",
"files": "Dateien", "files": "Dateien",
"finished": "fertiggestellt",
"font": "Schriftarten", "font": "Schriftarten",
"fontSize": "Schriftgröße", "fontSize": "Schriftgröße",
"foundNUpdate": "Update {count} gefunden", "foundNUpdate": "Update {count} gefunden",
@@ -72,6 +72,7 @@
"image": "Image", "image": "Image",
"imagesList": "Images", "imagesList": "Images",
"import": "Importieren", "import": "Importieren",
"inner": "Eingebaut",
"inputDomainHere": "Domain eingeben", "inputDomainHere": "Domain eingeben",
"install": "install", "install": "install",
"installDockerWithUrl": "Bitte installiere docker zuerst. https://docs.docker.com/engine/install", "installDockerWithUrl": "Bitte installiere docker zuerst. https://docs.docker.com/engine/install",
@@ -95,6 +96,7 @@
"maxRetryCount": "Anzahl an Verbindungsversuchen", "maxRetryCount": "Anzahl an Verbindungsversuchen",
"maxRetryCountEqual0": "Unbegrenzte Verbindungsversuche zum Server", "maxRetryCountEqual0": "Unbegrenzte Verbindungsversuche zum Server",
"min": "min", "min": "min",
"mission": "Mission",
"ms": "ms", "ms": "ms",
"name": "Name", "name": "Name",
"needRestart": "App muss neugestartet werden", "needRestart": "App muss neugestartet werden",
@@ -161,6 +163,7 @@
"sureDirEmpty": "Stelle sicher, dass der Ordner leer ist.", "sureDirEmpty": "Stelle sicher, dass der Ordner leer ist.",
"sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?", "sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?",
"sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?", "sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?",
"system": "Systeme",
"tag": "Tags", "tag": "Tags",
"terminal": "Terminal", "terminal": "Terminal",
"theme": "Themen", "theme": "Themen",

View File

@@ -44,7 +44,6 @@
"dockerStatusRunningAndStoppedFmt": "{runningCount} running, {stoppedCount} container stopped.", "dockerStatusRunningAndStoppedFmt": "{runningCount} running, {stoppedCount} container stopped.",
"dockerStatusRunningFmt": "{count} container running.", "dockerStatusRunningFmt": "{count} container running.",
"download": "Download", "download": "Download",
"downloadFinished": "Download finished",
"downloadStatus": "{percent}% of {size}", "downloadStatus": "{percent}% of {size}",
"edit": "Edit", "edit": "Edit",
"editor": "Editor", "editor": "Editor",
@@ -61,6 +60,7 @@
"fileNotExist": "{file} not exist", "fileNotExist": "{file} not exist",
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}", "fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
"files": "Files", "files": "Files",
"finished": "Finished",
"font": "Font", "font": "Font",
"fontSize": "Font size", "fontSize": "Font size",
"foundNUpdate": "Found {count} update", "foundNUpdate": "Found {count} update",
@@ -72,6 +72,7 @@
"image": "Image", "image": "Image",
"imagesList": "Images list", "imagesList": "Images list",
"import": "Import", "import": "Import",
"inner": "Inner",
"inputDomainHere": "Input Domain here", "inputDomainHere": "Input Domain here",
"install": "install", "install": "install",
"installDockerWithUrl": "Please https://docs.docker.com/engine/install docker first.", "installDockerWithUrl": "Please https://docs.docker.com/engine/install docker first.",
@@ -95,6 +96,7 @@
"maxRetryCount": "Number of server reconnection", "maxRetryCount": "Number of server reconnection",
"maxRetryCountEqual0": "Will retry again and again.", "maxRetryCountEqual0": "Will retry again and again.",
"min": "min", "min": "min",
"mission": "Mission",
"ms": "ms", "ms": "ms",
"name": "Name", "name": "Name",
"needRestart": "Need to restart app", "needRestart": "Need to restart app",
@@ -161,6 +163,7 @@
"sureDirEmpty": "Make sure dir is empty.", "sureDirEmpty": "Make sure dir is empty.",
"sureNoPwd": "Are you sure to use no password?", "sureNoPwd": "Are you sure to use no password?",
"sureToDeleteServer": "Are you sure to delete server [{server}]?", "sureToDeleteServer": "Are you sure to delete server [{server}]?",
"system": "System",
"tag": "Tags", "tag": "Tags",
"terminal": "Terminal", "terminal": "Terminal",
"theme": "Theme", "theme": "Theme",

View File

@@ -44,7 +44,6 @@
"dockerStatusRunningAndStoppedFmt": "{runningCount}个正在运行, {stoppedCount}个已停止", "dockerStatusRunningAndStoppedFmt": "{runningCount}个正在运行, {stoppedCount}个已停止",
"dockerStatusRunningFmt": "{count}个容器正在运行", "dockerStatusRunningFmt": "{count}个容器正在运行",
"download": "下载", "download": "下载",
"downloadFinished": "下载完成",
"downloadStatus": "{size} 的 {percent}%", "downloadStatus": "{size} 的 {percent}%",
"edit": "编辑", "edit": "编辑",
"editor": "编辑器", "editor": "编辑器",
@@ -61,6 +60,7 @@
"fileNotExist": "{file} 不存在", "fileNotExist": "{file} 不存在",
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}", "fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
"files": "文件", "files": "文件",
"finished": "已完成",
"font": "字体", "font": "字体",
"fontSize": "字体大小", "fontSize": "字体大小",
"foundNUpdate": "找到 {count} 个更新", "foundNUpdate": "找到 {count} 个更新",
@@ -72,6 +72,7 @@
"image": "镜像", "image": "镜像",
"imagesList": "镜像列表", "imagesList": "镜像列表",
"import": "导入", "import": "导入",
"inner": "内置",
"inputDomainHere": "在这里输入域名", "inputDomainHere": "在这里输入域名",
"install": "安装", "install": "安装",
"installDockerWithUrl": "请先 https://docs.docker.com/engine/install docker", "installDockerWithUrl": "请先 https://docs.docker.com/engine/install docker",
@@ -95,6 +96,7 @@
"maxRetryCount": "服务器尝试重连次数", "maxRetryCount": "服务器尝试重连次数",
"maxRetryCountEqual0": "会无限重试", "maxRetryCountEqual0": "会无限重试",
"min": "最小", "min": "最小",
"mission": "任务",
"ms": "毫秒", "ms": "毫秒",
"name": "名称", "name": "名称",
"needRestart": "需要重启 App", "needRestart": "需要重启 App",
@@ -161,6 +163,7 @@
"sureDirEmpty": "请确保文件夹为空", "sureDirEmpty": "请确保文件夹为空",
"sureNoPwd": "确认使用无密码?", "sureNoPwd": "确认使用无密码?",
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?", "sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
"system": "系统",
"tag": "标签", "tag": "标签",
"terminal": "终端", "terminal": "终端",
"theme": "主题", "theme": "主题",

View File

@@ -44,7 +44,6 @@
"dockerStatusRunningAndStoppedFmt": "{runningCount}個正在運行, {stoppedCount}個已停止", "dockerStatusRunningAndStoppedFmt": "{runningCount}個正在運行, {stoppedCount}個已停止",
"dockerStatusRunningFmt": "{count}個容器正在運行", "dockerStatusRunningFmt": "{count}個容器正在運行",
"download": "下載", "download": "下載",
"downloadFinished": "下載完成",
"downloadStatus": "{size} 的 {percent}%", "downloadStatus": "{size} 的 {percent}%",
"edit": "編輯", "edit": "編輯",
"editor": "編輯器", "editor": "編輯器",
@@ -61,6 +60,7 @@
"fileNotExist": "{file} 不存在", "fileNotExist": "{file} 不存在",
"fileTooLarge": "文件 '{file}' 過大 '{size}',超過了 {sizeMax}", "fileTooLarge": "文件 '{file}' 過大 '{size}',超過了 {sizeMax}",
"files": "文件", "files": "文件",
"finished": "已完成",
"font": "字體", "font": "字體",
"fontSize": "字體大小", "fontSize": "字體大小",
"foundNUpdate": "找到 {count} 個更新", "foundNUpdate": "找到 {count} 個更新",
@@ -72,6 +72,7 @@
"image": "鏡像", "image": "鏡像",
"imagesList": "鏡像列表", "imagesList": "鏡像列表",
"import": "導入", "import": "導入",
"inner": "內置",
"inputDomainHere": "在這裡輸入域名", "inputDomainHere": "在這裡輸入域名",
"install": "安裝", "install": "安裝",
"installDockerWithUrl": "請先 https://docs.docker.com/engine/install docker", "installDockerWithUrl": "請先 https://docs.docker.com/engine/install docker",
@@ -95,6 +96,7 @@
"maxRetryCount": "服務器嘗試重連次數", "maxRetryCount": "服務器嘗試重連次數",
"maxRetryCountEqual0": "會無限重試", "maxRetryCountEqual0": "會無限重試",
"min": "最小", "min": "最小",
"mission": "任務",
"ms": "毫秒", "ms": "毫秒",
"name": "名稱", "name": "名稱",
"needRestart": "需要重啓 App", "needRestart": "需要重啓 App",
@@ -161,6 +163,7 @@
"sureDirEmpty": "請確保文件夾為空", "sureDirEmpty": "請確保文件夾為空",
"sureNoPwd": "確認使用無密碼?", "sureNoPwd": "確認使用無密碼?",
"sureToDeleteServer": "你確定要刪除服務器 [{server}] 嗎?", "sureToDeleteServer": "你確定要刪除服務器 [{server}] 嗎?",
"system": "系統",
"tag": "标签", "tag": "标签",
"terminal": "终端機", "terminal": "终端機",
"theme": "主題", "theme": "主題",

View File

@@ -26,7 +26,7 @@ import 'ping.dart';
import 'private_key/list.dart'; import 'private_key/list.dart';
import 'server/tab.dart'; import 'server/tab.dart';
import 'setting.dart'; import 'setting.dart';
import 'sftp/downloaded.dart'; import 'sftp/local.dart';
import 'snippet/list.dart'; import 'snippet/list.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {

View File

@@ -2,7 +2,6 @@ import 'package:after_layout/after_layout.dart';
import 'package:circle_chart/circle_chart.dart'; import 'package:circle_chart/circle_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:toolbox/core/extension/navigator.dart'; import 'package:toolbox/core/extension/navigator.dart';
@@ -26,7 +25,7 @@ import '../../widget/round_rect_card.dart';
import '../../widget/url_text.dart'; import '../../widget/url_text.dart';
import '../docker.dart'; import '../docker.dart';
import '../pkg.dart'; import '../pkg.dart';
import '../sftp/view.dart'; import '../sftp/remote.dart';
import '../ssh.dart'; import '../ssh.dart';
import 'detail.dart'; import 'detail.dart';
import 'edit.dart'; import 'edit.dart';
@@ -80,9 +79,53 @@ class _ServerPageState extends State<ServerPage>
); );
} }
Widget _buildTagsSwitcher(ServerProvider pro) { Widget _buildBody() {
if (pro.tags.isEmpty) return placeholder; return RefreshIndicator(
final items = <String?>[null, ...pro.tags]; onRefresh: () async =>
await _serverProvider.refreshData(onlyFailed: true),
child: Consumer<ServerProvider>(
builder: (_, pro, __) {
if (!pro.tags.contains(_tag)) {
_tag = null;
}
if (pro.serverOrder.isEmpty) {
return Center(
child: Text(
_s.serverTabEmpty,
textAlign: TextAlign.center,
),
);
}
final filtered = pro.serverOrder
.where((e) => pro.servers.containsKey(e))
.where((e) =>
_tag == null ||
(pro.servers[e]?.spi.tags?.contains(_tag) ?? false))
.toList();
return ReorderableListView.builder(
header: _buildTagsSwitcher(pro.tags),
padding: const EdgeInsets.fromLTRB(7, 10, 7, 7),
onReorder: (oldIndex, newIndex) => setState(() {
pro.serverOrder.moveById(
filtered[oldIndex],
filtered[newIndex],
_settingStore.serverOrder,
);
}),
itemBuilder: (_, index) => _buildEachServerCard(
pro.servers[filtered[index]],
index,
),
itemCount: filtered.length,
);
},
),
);
}
Widget _buildTagsSwitcher(List<String> tags) {
if (tags.isEmpty) return placeholder;
final items = <String?>[null, ...tags];
return Container( return Container(
height: 37, height: 37,
width: _media.size.width, width: _media.size.width,
@@ -125,51 +168,6 @@ class _ServerPageState extends State<ServerPage>
); );
} }
Widget _buildBody() {
return RefreshIndicator(
onRefresh: () async =>
await _serverProvider.refreshData(onlyFailed: true),
child: Consumer<ServerProvider>(
builder: (_, pro, __) {
if (!pro.tags.contains(_tag)) {
_tag = null;
}
if (pro.serverOrder.isEmpty) {
return Center(
child: Text(
_s.serverTabEmpty,
textAlign: TextAlign.center,
),
);
}
final filtered = pro.serverOrder
.where((e) => pro.servers.containsKey(e))
.where((e) =>
_tag == null ||
(pro.servers[e]?.spi.tags?.contains(_tag) ?? false))
.toList();
return AnimationLimiter(
key: ValueKey(_tag),
child: ReorderableListView.builder(
header: _buildTagsSwitcher(pro),
padding: const EdgeInsets.fromLTRB(7, 10, 7, 7),
physics: const AlwaysScrollableScrollPhysics(),
onReorder: (oldIndex, newIndex) => setState(() {
pro.serverOrder.moveById(
filtered[oldIndex],
filtered[newIndex],
_settingStore.serverOrder,
);
}),
itemBuilder: (context, index) =>
_buildEachServerCard(pro.servers[filtered[index]], index),
itemCount: filtered.length,
));
},
),
);
}
Widget _buildEachServerCard(Server? si, int index) { Widget _buildEachServerCard(Server? si, int index) {
if (si == null) { if (si == null) {
return placeholder; return placeholder;
@@ -180,18 +178,12 @@ class _ServerPageState extends State<ServerPage>
ServerDetailPage(si.spi.id), ServerDetailPage(si.spi.id),
'server detail page', 'server detail page',
).go(context), ).go(context),
child: AnimationConfiguration.staggeredList( child: RoundRectCard(
position: index, Padding(
duration: const Duration(milliseconds: 375), padding: const EdgeInsets.all(13),
child: SlideAnimation( child: _buildRealServerCard(si.status, si.state, si.spi),
verticalOffset: 50.0, ),
child: FadeInAnimation( ),
child: RoundRectCard(
Padding(
padding: const EdgeInsets.all(13),
child: _buildRealServerCard(si.status, si.state, si.spi),
),
)))),
); );
} }

View File

@@ -21,7 +21,7 @@ import '../../../data/model/app/path_with_prefix.dart';
import '../../../data/res/path.dart'; import '../../../data/res/path.dart';
import '../../../data/res/ui.dart'; import '../../../data/res/ui.dart';
import '../../widget/fade_in.dart'; import '../../widget/fade_in.dart';
import 'downloading.dart'; import 'mission.dart';
class SFTPDownloadedPage extends StatefulWidget { class SFTPDownloadedPage extends StatefulWidget {
final bool isPickFile; final bool isPickFile;

View File

@@ -31,7 +31,7 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
_s.download, _s.mission,
style: textSize18, style: textSize18,
), ),
), ),
@@ -61,7 +61,11 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
{Widget? trailing}) { {Widget? trailing}) {
return RoundRectCard( return RoundRectCard(
ListTile( ListTile(
title: Text(status.fileName), title: Text(
status.fileName,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
subtitle: subtitle == null subtitle: subtitle == null
? null ? null
: Text( : Text(
@@ -81,21 +85,26 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
switch (status.status) { switch (status.status) {
case SftpWorkerStatus.finished: case SftpWorkerStatus.finished:
final time = status.spentTime.toString(); final time = status.spentTime.toString();
final str = '${_s.finished} ${_s.spentTime(
time == 'null' ? _s.unknown : (time.substring(0, time.length - 7)),
)}';
return _wrapInCard( return _wrapInCard(
status, status,
'${_s.downloadFinished} ${_s.spentTime(time == 'null' ? _s.unknown : (time.substring(0, time.length - 7)))}', str,
trailing: IconButton( trailing: IconButton(
onPressed: () => shareFiles(context, [status.item.localPath]), onPressed: () => shareFiles(context, [status.item.localPath]),
icon: const Icon(Icons.open_in_new), icon: const Icon(Icons.open_in_new),
), ),
); );
case SftpWorkerStatus.downloading: case SftpWorkerStatus.downloading:
final percentStr = (status.progress ?? 0.0).toStringAsFixed(2);
final percent = (status.progress ?? 0) / 100;
final size = (status.size ?? 0).convertBytes;
return _wrapInCard( return _wrapInCard(
status, status,
_s.downloadStatus((status.progress ?? 0.0).toStringAsFixed(2), _s.downloadStatus(percentStr, size),
(status.size ?? 0).convertBytes), trailing: CircularProgressIndicator(value: percent),
trailing: );
CircularProgressIndicator(value: (status.progress ?? 0) / 100));
case SftpWorkerStatus.preparing: case SftpWorkerStatus.preparing:
return _wrapInCard(status, _s.sftpDlPrepare, trailing: loadingIcon); return _wrapInCard(status, _s.sftpDlPrepare, trailing: loadingIcon);
case SftpWorkerStatus.sshConnectted: case SftpWorkerStatus.sshConnectted:
@@ -104,10 +113,7 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
return _wrapInCard( return _wrapInCard(
status, status,
_s.unknown, _s.unknown,
trailing: const Icon( trailing: const Icon(Icons.error, size: 40),
Icons.error,
size: 40,
),
); );
} }
} }

View File

@@ -1,4 +1,4 @@
import 'dart:io'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
@@ -8,7 +8,7 @@ import 'package:toolbox/core/extension/navigator.dart';
import 'package:toolbox/core/extension/sftpfile.dart'; import 'package:toolbox/core/extension/sftpfile.dart';
import 'package:toolbox/data/res/misc.dart'; import 'package:toolbox/data/res/misc.dart';
import 'package:toolbox/view/page/editor.dart'; import 'package:toolbox/view/page/editor.dart';
import 'package:toolbox/view/page/sftp/downloaded.dart'; import 'package:toolbox/view/page/sftp/local.dart';
import '../../../core/extension/numx.dart'; import '../../../core/extension/numx.dart';
import '../../../core/extension/stringx.dart'; import '../../../core/extension/stringx.dart';
@@ -24,12 +24,11 @@ import '../../../data/provider/server.dart';
import '../../../data/provider/sftp.dart'; import '../../../data/provider/sftp.dart';
import '../../../data/res/path.dart'; import '../../../data/res/path.dart';
import '../../../data/res/ui.dart'; import '../../../data/res/ui.dart';
import '../../../data/store/private_key.dart';
import '../../../locator.dart'; import '../../../locator.dart';
import '../../widget/fade_in.dart'; import '../../widget/fade_in.dart';
import '../../widget/input_field.dart'; import '../../widget/input_field.dart';
import '../../widget/two_line_text.dart'; import '../../widget/two_line_text.dart';
import 'downloading.dart'; import 'mission.dart';
class SFTPPage extends StatefulWidget { class SFTPPage extends StatefulWidget {
final ServerPrivateInfo spi; final ServerPrivateInfo spi;
@@ -44,6 +43,8 @@ class _SFTPPageState extends State<SFTPPage> {
final SftpBrowserStatus _status = SftpBrowserStatus(); final SftpBrowserStatus _status = SftpBrowserStatus();
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final _sftp = locator<SftpProvider>();
late S _s; late S _s;
ServerState? _state; ServerState? _state;
@@ -117,12 +118,38 @@ class _SFTPPageState extends State<SFTPPage> {
Widget _buildUploadBtn() { Widget _buildUploadBtn() {
return IconButton( return IconButton(
onPressed: () async { onPressed: () async {
final path = await AppRoute( final idx = await showRoundDialog(
const SFTPDownloadedPage( context: context,
isPickFile: true, child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.open_in_new),
title: Text(_s.system),
onTap: () => context.pop(1),
), ),
'sftp dled pick') ListTile(
.go<String>(context); leading: const Icon(Icons.folder),
title: Text(_s.inner),
onTap: () => context.pop(0),
),
],
));
final path = await () async {
switch (idx) {
case 0:
return await AppRoute(
const SFTPDownloadedPage(
isPickFile: true,
),
'sftp dled pick')
.go<String>(context);
case 1:
return await pickOneFile();
default:
return null;
}
}();
if (path == null) { if (path == null) {
return; return;
} }
@@ -131,7 +158,7 @@ class _SFTPPageState extends State<SFTPPage> {
showSnackBar(context, const Text('remote path is null')); showSnackBar(context, const Text('remote path is null'));
return; return;
} }
locator<SftpProvider>().add( _sftp.add(
SftpReqItem(widget.spi, remotePath, path), SftpReqItem(widget.spi, remotePath, path),
SftpReqType.upload, SftpReqType.upload,
); );
@@ -302,39 +329,20 @@ class _SFTPPageState extends State<SFTPPage> {
return; return;
} }
final file = await _status.client!.open( final remotePath = _getRemotePath(name);
_getRemotePath(name), final localPath = await _getLocalPath(remotePath);
mode: SftpFileOpenMode.read | SftpFileOpenMode.write, final completer = Completer();
); final req = SftpReqItem(widget.spi, remotePath, localPath);
final localPath = '${(await sftpDir).path}${_getRemotePath(name)}'; _sftp.add(req, SftpReqType.download, completer: completer);
await Directory(localPath.substring(0, localPath.lastIndexOf('/'))) await completer.future;
.create(recursive: true);
final local = File(localPath);
if (await local.exists()) {
await local.delete();
}
final localFile = local.openWrite(mode: FileMode.append);
const defaultChunkSize = 1024 * 1024;
final chunkSize = size > defaultChunkSize ? defaultChunkSize : size;
for (var i = 0; i < size; i += chunkSize) {
final fileData = file.read(length: chunkSize);
await for (var form in fileData) {
localFile.add(form);
}
}
await localFile.close();
context.pop();
final result = await AppRoute( final result = await AppRoute(
EditorPage(path: localPath), EditorPage(path: localPath),
'SFTP edit', 'SFTP edit',
).go<String>(context); ).go<String>(context);
if (result != null) { if (result != null) {
await local.writeAsString(result); _sftp.add(req, SftpReqType.upload);
await file.writeBytes(result.uint8List);
showSnackBar(context, Text(_s.saved));
} }
await file.close();
} }
void _download(BuildContext context, SftpName name) { void _download(BuildContext context, SftpName name) {
@@ -351,18 +359,14 @@ class _SFTPPageState extends State<SFTPPage> {
onPressed: () async { onPressed: () async {
context.pop(); context.pop();
final remotePath = _getRemotePath(name); final remotePath = _getRemotePath(name);
final local = '${(await sftpDir).path}$remotePath';
final pubKeyId = widget.spi.pubKeyId;
final key = locator<PrivateKeyStore>().get(pubKeyId)?.privateKey;
locator<SftpProvider>().add( _sftp.add(
SftpReqItem( SftpReqItem(
widget.spi, widget.spi,
remotePath, remotePath,
local, await _getLocalPath(remotePath),
), ),
SftpReqType.download, SftpReqType.download,
key: key,
); );
context.pop(); context.pop();
@@ -564,6 +568,10 @@ class _SFTPPageState extends State<SFTPPage> {
return pathJoin(prePath, name.filename); return pathJoin(prePath, name.filename);
} }
Future<String> _getLocalPath(String remotePath) async {
return '${(await sftpDir).path}$remotePath';
}
Future<void> _listDir({String? path, SSHClient? client}) async { Future<void> _listDir({String? path, SSHClient? client}) async {
if (_status.isBusy) { if (_status.isBusy) {
return; return;

View File

@@ -23,7 +23,7 @@ import '../../data/res/terminal.dart';
import '../../data/res/virtual_key.dart'; import '../../data/res/virtual_key.dart';
import '../../data/store/setting.dart'; import '../../data/store/setting.dart';
import '../../locator.dart'; import '../../locator.dart';
import 'sftp/view.dart'; import 'sftp/remote.dart';
class SSHPage extends StatefulWidget { class SSHPage extends StatefulWidget {
final ServerPrivateInfo spi; final ServerPrivateInfo spi;

View File

@@ -3,8 +3,13 @@ import 'package:flutter/material.dart';
/// 渐隐渐显实现 /// 渐隐渐显实现
class FadeIn extends StatefulWidget { class FadeIn extends StatefulWidget {
final Widget child; final Widget child;
final Duration duration;
const FadeIn({Key? key, required this.child}) : super(key: key); const FadeIn({
Key? key,
required this.child,
this.duration = const Duration(milliseconds: 377),
}) : super(key: key);
@override @override
_MyFadeInState createState() => _MyFadeInState(); _MyFadeInState createState() => _MyFadeInState();
@@ -19,7 +24,7 @@ class _MyFadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
super.initState(); super.initState();
_controller = AnimationController( _controller = AnimationController(
vsync: this, vsync: this,
duration: const Duration(milliseconds: 377), duration: widget.duration,
); );
_animation = Tween( _animation = Tween(
begin: 0.0, begin: 0.0,

View File

@@ -475,9 +475,9 @@
baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */; baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 341; CURRENT_PROJECT_VERSION = 343;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0.341; MARKETING_VERSION = 1.0.343;
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -490,9 +490,9 @@
baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */; baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 341; CURRENT_PROJECT_VERSION = 343;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0.341; MARKETING_VERSION = 1.0.343;
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -505,9 +505,9 @@
baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */; baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 341; CURRENT_PROJECT_VERSION = 343;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0.341; MARKETING_VERSION = 1.0.343;
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@@ -142,6 +142,19 @@ Future<void> flutterBuildMacOS() async {
Future<void> flutterBuildAndroid() async { Future<void> flutterBuildAndroid() async {
await flutterBuild('apk'); await flutterBuild('apk');
await killJava(); await killJava();
await scp2CDN();
}
Future<void> scp2CDN() async {
final result = await Process.run('scp', [
apkPath,
'custcdn:/usr/share/caddy/uploads/${appName}_${build}_Arm64.apk'
]);
print(result.stdout);
if (result.exitCode != 0) {
print(result.stderr);
exit(1);
}
} }
Future<void> changeAppleVersion() async { Future<void> changeAppleVersion() async {

View File

@@ -372,14 +372,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.14" version: "2.0.14"
flutter_staggered_animations:
dependency: "direct main"
description:
name: flutter_staggered_animations
sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter

View File

@@ -66,7 +66,6 @@ dependencies:
highlight: ^0.7.0 highlight: ^0.7.0
flutter_highlight: ^0.7.0 flutter_highlight: ^0.7.0
code_text_field: ^1.1.0 code_text_field: ^1.1.0
flutter_staggered_animations: ^1.1.1
dev_dependencies: dev_dependencies:
flutter_native_splash: ^2.1.6 flutter_native_splash: ^2.1.6