mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
#33 new: upload file to sftp from file picker
This commit is contained in:
@@ -360,12 +360,6 @@ abstract class S {
|
||||
/// **'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.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -462,6 +456,12 @@ abstract class S {
|
||||
/// **'Files'**
|
||||
String get files;
|
||||
|
||||
/// No description provided for @finished.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Finished'**
|
||||
String get finished;
|
||||
|
||||
/// No description provided for @font.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -528,6 +528,12 @@ abstract class S {
|
||||
/// **'Import'**
|
||||
String get import;
|
||||
|
||||
/// No description provided for @inner.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Inner'**
|
||||
String get inner;
|
||||
|
||||
/// No description provided for @inputDomainHere.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -666,6 +672,12 @@ abstract class S {
|
||||
/// **'min'**
|
||||
String get min;
|
||||
|
||||
/// No description provided for @mission.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Mission'**
|
||||
String get mission;
|
||||
|
||||
/// No description provided for @ms.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -1062,6 +1074,12 @@ abstract class S {
|
||||
/// **'Are you sure to delete server [{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.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -144,9 +144,6 @@ class SDe extends S {
|
||||
@override
|
||||
String get download => 'Download';
|
||||
|
||||
@override
|
||||
String get downloadFinished => 'Download abgeschlossen';
|
||||
|
||||
@override
|
||||
String downloadStatus(Object percent, Object size) {
|
||||
return '$percent% von $size';
|
||||
@@ -201,6 +198,9 @@ class SDe extends S {
|
||||
@override
|
||||
String get files => 'Dateien';
|
||||
|
||||
@override
|
||||
String get finished => 'fertiggestellt';
|
||||
|
||||
@override
|
||||
String get font => 'Schriftarten';
|
||||
|
||||
@@ -238,6 +238,9 @@ class SDe extends S {
|
||||
@override
|
||||
String get import => 'Importieren';
|
||||
|
||||
@override
|
||||
String get inner => 'Eingebaut';
|
||||
|
||||
@override
|
||||
String get inputDomainHere => 'Domain eingeben';
|
||||
|
||||
@@ -311,6 +314,9 @@ class SDe extends S {
|
||||
@override
|
||||
String get min => 'min';
|
||||
|
||||
@override
|
||||
String get mission => 'Mission';
|
||||
|
||||
@override
|
||||
String get ms => 'ms';
|
||||
|
||||
@@ -521,6 +527,9 @@ class SDe extends S {
|
||||
return 'Bist du sicher, dass du [$server] löschen willst?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get system => 'Systeme';
|
||||
|
||||
@override
|
||||
String get tag => 'Tags';
|
||||
|
||||
|
||||
@@ -144,9 +144,6 @@ class SEn extends S {
|
||||
@override
|
||||
String get download => 'Download';
|
||||
|
||||
@override
|
||||
String get downloadFinished => 'Download finished';
|
||||
|
||||
@override
|
||||
String downloadStatus(Object percent, Object size) {
|
||||
return '$percent% of $size';
|
||||
@@ -201,6 +198,9 @@ class SEn extends S {
|
||||
@override
|
||||
String get files => 'Files';
|
||||
|
||||
@override
|
||||
String get finished => 'Finished';
|
||||
|
||||
@override
|
||||
String get font => 'Font';
|
||||
|
||||
@@ -238,6 +238,9 @@ class SEn extends S {
|
||||
@override
|
||||
String get import => 'Import';
|
||||
|
||||
@override
|
||||
String get inner => 'Inner';
|
||||
|
||||
@override
|
||||
String get inputDomainHere => 'Input Domain here';
|
||||
|
||||
@@ -311,6 +314,9 @@ class SEn extends S {
|
||||
@override
|
||||
String get min => 'min';
|
||||
|
||||
@override
|
||||
String get mission => 'Mission';
|
||||
|
||||
@override
|
||||
String get ms => 'ms';
|
||||
|
||||
@@ -521,6 +527,9 @@ class SEn extends S {
|
||||
return 'Are you sure to delete server [$server]?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get system => 'System';
|
||||
|
||||
@override
|
||||
String get tag => 'Tags';
|
||||
|
||||
|
||||
@@ -144,9 +144,6 @@ class SZh extends S {
|
||||
@override
|
||||
String get download => '下载';
|
||||
|
||||
@override
|
||||
String get downloadFinished => '下载完成';
|
||||
|
||||
@override
|
||||
String downloadStatus(Object percent, Object size) {
|
||||
return '$size 的 $percent%';
|
||||
@@ -201,6 +198,9 @@ class SZh extends S {
|
||||
@override
|
||||
String get files => '文件';
|
||||
|
||||
@override
|
||||
String get finished => '已完成';
|
||||
|
||||
@override
|
||||
String get font => '字体';
|
||||
|
||||
@@ -238,6 +238,9 @@ class SZh extends S {
|
||||
@override
|
||||
String get import => '导入';
|
||||
|
||||
@override
|
||||
String get inner => '内置';
|
||||
|
||||
@override
|
||||
String get inputDomainHere => '在这里输入域名';
|
||||
|
||||
@@ -311,6 +314,9 @@ class SZh extends S {
|
||||
@override
|
||||
String get min => '最小';
|
||||
|
||||
@override
|
||||
String get mission => '任务';
|
||||
|
||||
@override
|
||||
String get ms => '毫秒';
|
||||
|
||||
@@ -521,6 +527,9 @@ class SZh extends S {
|
||||
return '你确定要删除服务器 [$server] 吗?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get system => '系统';
|
||||
|
||||
@override
|
||||
String get tag => '标签';
|
||||
|
||||
@@ -751,9 +760,6 @@ class SZhTw extends SZh {
|
||||
@override
|
||||
String get download => '下載';
|
||||
|
||||
@override
|
||||
String get downloadFinished => '下載完成';
|
||||
|
||||
@override
|
||||
String downloadStatus(Object percent, Object size) {
|
||||
return '$size 的 $percent%';
|
||||
@@ -808,6 +814,9 @@ class SZhTw extends SZh {
|
||||
@override
|
||||
String get files => '文件';
|
||||
|
||||
@override
|
||||
String get finished => '已完成';
|
||||
|
||||
@override
|
||||
String get font => '字體';
|
||||
|
||||
@@ -845,6 +854,9 @@ class SZhTw extends SZh {
|
||||
@override
|
||||
String get import => '導入';
|
||||
|
||||
@override
|
||||
String get inner => '內置';
|
||||
|
||||
@override
|
||||
String get inputDomainHere => '在這裡輸入域名';
|
||||
|
||||
@@ -918,6 +930,9 @@ class SZhTw extends SZh {
|
||||
@override
|
||||
String get min => '最小';
|
||||
|
||||
@override
|
||||
String get mission => '任務';
|
||||
|
||||
@override
|
||||
String get ms => '毫秒';
|
||||
|
||||
@@ -1128,6 +1143,9 @@ class SZhTw extends SZh {
|
||||
return '你確定要刪除服務器 [$server] 嗎?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get system => '系統';
|
||||
|
||||
@override
|
||||
String get tag => '标签';
|
||||
|
||||
|
||||
@@ -360,7 +360,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 341;
|
||||
CURRENT_PROJECT_VERSION = 343;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -368,7 +368,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.341;
|
||||
MARKETING_VERSION = 1.0.343;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -491,7 +491,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 341;
|
||||
CURRENT_PROJECT_VERSION = 343;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -499,7 +499,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.341;
|
||||
MARKETING_VERSION = 1.0.343;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -516,7 +516,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 341;
|
||||
CURRENT_PROJECT_VERSION = 343;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -524,7 +524,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.341;
|
||||
MARKETING_VERSION = 1.0.343;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../server/server_private_info.dart';
|
||||
import 'worker.dart';
|
||||
|
||||
@@ -24,6 +26,7 @@ class SftpReqStatus {
|
||||
final SftpReqItem item;
|
||||
final void Function() notifyListeners;
|
||||
late SftpWorker worker;
|
||||
final Completer? completer;
|
||||
|
||||
String get fileName => item.localPath.split('/').last;
|
||||
|
||||
@@ -38,12 +41,11 @@ class SftpReqStatus {
|
||||
required this.item,
|
||||
required this.notifyListeners,
|
||||
required SftpReqType type,
|
||||
String? key,
|
||||
this.completer,
|
||||
}) : id = DateTime.now().microsecondsSinceEpoch {
|
||||
worker = SftpWorker(
|
||||
onNotify: onNotify,
|
||||
item: item,
|
||||
privateKey: key,
|
||||
type: type,
|
||||
);
|
||||
worker.init();
|
||||
@@ -61,6 +63,7 @@ class SftpReqStatus {
|
||||
status = event;
|
||||
if (status == SftpWorkerStatus.finished) {
|
||||
worker.dispose();
|
||||
completer?.complete();
|
||||
}
|
||||
break;
|
||||
case double:
|
||||
|
||||
@@ -12,7 +12,6 @@ import 'req.dart';
|
||||
class SftpWorker {
|
||||
final Function(Object event) onNotify;
|
||||
final SftpReqItem item;
|
||||
final String? privateKey;
|
||||
final SftpReqType type;
|
||||
|
||||
final worker = Worker();
|
||||
@@ -21,7 +20,6 @@ class SftpWorker {
|
||||
required this.onNotify,
|
||||
required this.item,
|
||||
required this.type,
|
||||
this.privateKey,
|
||||
});
|
||||
|
||||
void dispose() {
|
||||
@@ -37,7 +35,7 @@ class SftpWorker {
|
||||
isolateMessageHandler,
|
||||
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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:toolbox/core/provider_base.dart';
|
||||
|
||||
import '../model/sftp/req.dart';
|
||||
@@ -25,12 +27,12 @@ class SftpProvider extends ProviderBase {
|
||||
return found.first;
|
||||
}
|
||||
|
||||
void add(SftpReqItem item, SftpReqType type, {String? key}) {
|
||||
void add(SftpReqItem item, SftpReqType type, {Completer? completer}) {
|
||||
_status.add(SftpReqStatus(
|
||||
item: item,
|
||||
notifyListeners: notifyListeners,
|
||||
key: key,
|
||||
type: type,
|
||||
completer: completer,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
class BuildData {
|
||||
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 buildAt = "2023-05-31 19:23:57.263324";
|
||||
static const int modifications = 6;
|
||||
static const String buildAt = "2023-06-01 16:27:31.059809";
|
||||
static const int modifications = 4;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
"dockerStatusRunningAndStoppedFmt": "{runningCount} aktiv, {stoppedCount} container gestoppt.",
|
||||
"dockerStatusRunningFmt": "{count} Container aktiv",
|
||||
"download": "Download",
|
||||
"downloadFinished": "Download abgeschlossen",
|
||||
"downloadStatus": "{percent}% von {size}",
|
||||
"edit": "Bearbeiten",
|
||||
"editor": "Redakteure",
|
||||
@@ -61,6 +60,7 @@
|
||||
"fileNotExist": "{file} existiert nicht",
|
||||
"fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}",
|
||||
"files": "Dateien",
|
||||
"finished": "fertiggestellt",
|
||||
"font": "Schriftarten",
|
||||
"fontSize": "Schriftgröße",
|
||||
"foundNUpdate": "Update {count} gefunden",
|
||||
@@ -72,6 +72,7 @@
|
||||
"image": "Image",
|
||||
"imagesList": "Images",
|
||||
"import": "Importieren",
|
||||
"inner": "Eingebaut",
|
||||
"inputDomainHere": "Domain eingeben",
|
||||
"install": "install",
|
||||
"installDockerWithUrl": "Bitte installiere docker zuerst. https://docs.docker.com/engine/install",
|
||||
@@ -95,6 +96,7 @@
|
||||
"maxRetryCount": "Anzahl an Verbindungsversuchen",
|
||||
"maxRetryCountEqual0": "Unbegrenzte Verbindungsversuche zum Server",
|
||||
"min": "min",
|
||||
"mission": "Mission",
|
||||
"ms": "ms",
|
||||
"name": "Name",
|
||||
"needRestart": "App muss neugestartet werden",
|
||||
@@ -161,6 +163,7 @@
|
||||
"sureDirEmpty": "Stelle sicher, dass der Ordner leer ist.",
|
||||
"sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?",
|
||||
"sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?",
|
||||
"system": "Systeme",
|
||||
"tag": "Tags",
|
||||
"terminal": "Terminal",
|
||||
"theme": "Themen",
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
"dockerStatusRunningAndStoppedFmt": "{runningCount} running, {stoppedCount} container stopped.",
|
||||
"dockerStatusRunningFmt": "{count} container running.",
|
||||
"download": "Download",
|
||||
"downloadFinished": "Download finished",
|
||||
"downloadStatus": "{percent}% of {size}",
|
||||
"edit": "Edit",
|
||||
"editor": "Editor",
|
||||
@@ -61,6 +60,7 @@
|
||||
"fileNotExist": "{file} not exist",
|
||||
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
|
||||
"files": "Files",
|
||||
"finished": "Finished",
|
||||
"font": "Font",
|
||||
"fontSize": "Font size",
|
||||
"foundNUpdate": "Found {count} update",
|
||||
@@ -72,6 +72,7 @@
|
||||
"image": "Image",
|
||||
"imagesList": "Images list",
|
||||
"import": "Import",
|
||||
"inner": "Inner",
|
||||
"inputDomainHere": "Input Domain here",
|
||||
"install": "install",
|
||||
"installDockerWithUrl": "Please https://docs.docker.com/engine/install docker first.",
|
||||
@@ -95,6 +96,7 @@
|
||||
"maxRetryCount": "Number of server reconnection",
|
||||
"maxRetryCountEqual0": "Will retry again and again.",
|
||||
"min": "min",
|
||||
"mission": "Mission",
|
||||
"ms": "ms",
|
||||
"name": "Name",
|
||||
"needRestart": "Need to restart app",
|
||||
@@ -161,6 +163,7 @@
|
||||
"sureDirEmpty": "Make sure dir is empty.",
|
||||
"sureNoPwd": "Are you sure to use no password?",
|
||||
"sureToDeleteServer": "Are you sure to delete server [{server}]?",
|
||||
"system": "System",
|
||||
"tag": "Tags",
|
||||
"terminal": "Terminal",
|
||||
"theme": "Theme",
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
"dockerStatusRunningAndStoppedFmt": "{runningCount}个正在运行, {stoppedCount}个已停止",
|
||||
"dockerStatusRunningFmt": "{count}个容器正在运行",
|
||||
"download": "下载",
|
||||
"downloadFinished": "下载完成",
|
||||
"downloadStatus": "{size} 的 {percent}%",
|
||||
"edit": "编辑",
|
||||
"editor": "编辑器",
|
||||
@@ -61,6 +60,7 @@
|
||||
"fileNotExist": "{file} 不存在",
|
||||
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
|
||||
"files": "文件",
|
||||
"finished": "已完成",
|
||||
"font": "字体",
|
||||
"fontSize": "字体大小",
|
||||
"foundNUpdate": "找到 {count} 个更新",
|
||||
@@ -72,6 +72,7 @@
|
||||
"image": "镜像",
|
||||
"imagesList": "镜像列表",
|
||||
"import": "导入",
|
||||
"inner": "内置",
|
||||
"inputDomainHere": "在这里输入域名",
|
||||
"install": "安装",
|
||||
"installDockerWithUrl": "请先 https://docs.docker.com/engine/install docker",
|
||||
@@ -95,6 +96,7 @@
|
||||
"maxRetryCount": "服务器尝试重连次数",
|
||||
"maxRetryCountEqual0": "会无限重试",
|
||||
"min": "最小",
|
||||
"mission": "任务",
|
||||
"ms": "毫秒",
|
||||
"name": "名称",
|
||||
"needRestart": "需要重启 App",
|
||||
@@ -161,6 +163,7 @@
|
||||
"sureDirEmpty": "请确保文件夹为空",
|
||||
"sureNoPwd": "确认使用无密码?",
|
||||
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
|
||||
"system": "系统",
|
||||
"tag": "标签",
|
||||
"terminal": "终端",
|
||||
"theme": "主题",
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
"dockerStatusRunningAndStoppedFmt": "{runningCount}個正在運行, {stoppedCount}個已停止",
|
||||
"dockerStatusRunningFmt": "{count}個容器正在運行",
|
||||
"download": "下載",
|
||||
"downloadFinished": "下載完成",
|
||||
"downloadStatus": "{size} 的 {percent}%",
|
||||
"edit": "編輯",
|
||||
"editor": "編輯器",
|
||||
@@ -61,6 +60,7 @@
|
||||
"fileNotExist": "{file} 不存在",
|
||||
"fileTooLarge": "文件 '{file}' 過大 '{size}',超過了 {sizeMax}",
|
||||
"files": "文件",
|
||||
"finished": "已完成",
|
||||
"font": "字體",
|
||||
"fontSize": "字體大小",
|
||||
"foundNUpdate": "找到 {count} 個更新",
|
||||
@@ -72,6 +72,7 @@
|
||||
"image": "鏡像",
|
||||
"imagesList": "鏡像列表",
|
||||
"import": "導入",
|
||||
"inner": "內置",
|
||||
"inputDomainHere": "在這裡輸入域名",
|
||||
"install": "安裝",
|
||||
"installDockerWithUrl": "請先 https://docs.docker.com/engine/install docker",
|
||||
@@ -95,6 +96,7 @@
|
||||
"maxRetryCount": "服務器嘗試重連次數",
|
||||
"maxRetryCountEqual0": "會無限重試",
|
||||
"min": "最小",
|
||||
"mission": "任務",
|
||||
"ms": "毫秒",
|
||||
"name": "名稱",
|
||||
"needRestart": "需要重啓 App",
|
||||
@@ -161,6 +163,7 @@
|
||||
"sureDirEmpty": "請確保文件夾為空",
|
||||
"sureNoPwd": "確認使用無密碼?",
|
||||
"sureToDeleteServer": "你確定要刪除服務器 [{server}] 嗎?",
|
||||
"system": "系統",
|
||||
"tag": "标签",
|
||||
"terminal": "终端機",
|
||||
"theme": "主題",
|
||||
|
||||
@@ -26,7 +26,7 @@ import 'ping.dart';
|
||||
import 'private_key/list.dart';
|
||||
import 'server/tab.dart';
|
||||
import 'setting.dart';
|
||||
import 'sftp/downloaded.dart';
|
||||
import 'sftp/local.dart';
|
||||
import 'snippet/list.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:after_layout/after_layout.dart';
|
||||
import 'package:circle_chart/circle_chart.dart';
|
||||
import 'package:flutter/material.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:provider/provider.dart';
|
||||
import 'package:toolbox/core/extension/navigator.dart';
|
||||
@@ -26,7 +25,7 @@ import '../../widget/round_rect_card.dart';
|
||||
import '../../widget/url_text.dart';
|
||||
import '../docker.dart';
|
||||
import '../pkg.dart';
|
||||
import '../sftp/view.dart';
|
||||
import '../sftp/remote.dart';
|
||||
import '../ssh.dart';
|
||||
import 'detail.dart';
|
||||
import 'edit.dart';
|
||||
@@ -80,9 +79,53 @@ class _ServerPageState extends State<ServerPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTagsSwitcher(ServerProvider pro) {
|
||||
if (pro.tags.isEmpty) return placeholder;
|
||||
final items = <String?>[null, ...pro.tags];
|
||||
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 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(
|
||||
height: 37,
|
||||
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) {
|
||||
if (si == null) {
|
||||
return placeholder;
|
||||
@@ -180,18 +178,12 @@ class _ServerPageState extends State<ServerPage>
|
||||
ServerDetailPage(si.spi.id),
|
||||
'server detail page',
|
||||
).go(context),
|
||||
child: AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 50.0,
|
||||
child: FadeInAnimation(
|
||||
child: RoundRectCard(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(13),
|
||||
child: _buildRealServerCard(si.status, si.state, si.spi),
|
||||
),
|
||||
)))),
|
||||
child: RoundRectCard(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(13),
|
||||
child: _buildRealServerCard(si.status, si.state, si.spi),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import '../../../data/model/app/path_with_prefix.dart';
|
||||
import '../../../data/res/path.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../widget/fade_in.dart';
|
||||
import 'downloading.dart';
|
||||
import 'mission.dart';
|
||||
|
||||
class SFTPDownloadedPage extends StatefulWidget {
|
||||
final bool isPickFile;
|
||||
@@ -31,7 +31,7 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
_s.download,
|
||||
_s.mission,
|
||||
style: textSize18,
|
||||
),
|
||||
),
|
||||
@@ -61,7 +61,11 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
|
||||
{Widget? trailing}) {
|
||||
return RoundRectCard(
|
||||
ListTile(
|
||||
title: Text(status.fileName),
|
||||
title: Text(
|
||||
status.fileName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
subtitle: subtitle == null
|
||||
? null
|
||||
: Text(
|
||||
@@ -81,21 +85,26 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
|
||||
switch (status.status) {
|
||||
case SftpWorkerStatus.finished:
|
||||
final time = status.spentTime.toString();
|
||||
final str = '${_s.finished} ${_s.spentTime(
|
||||
time == 'null' ? _s.unknown : (time.substring(0, time.length - 7)),
|
||||
)}';
|
||||
return _wrapInCard(
|
||||
status,
|
||||
'${_s.downloadFinished} ${_s.spentTime(time == 'null' ? _s.unknown : (time.substring(0, time.length - 7)))}',
|
||||
str,
|
||||
trailing: IconButton(
|
||||
onPressed: () => shareFiles(context, [status.item.localPath]),
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
),
|
||||
);
|
||||
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(
|
||||
status,
|
||||
_s.downloadStatus((status.progress ?? 0.0).toStringAsFixed(2),
|
||||
(status.size ?? 0).convertBytes),
|
||||
trailing:
|
||||
CircularProgressIndicator(value: (status.progress ?? 0) / 100));
|
||||
status,
|
||||
_s.downloadStatus(percentStr, size),
|
||||
trailing: CircularProgressIndicator(value: percent),
|
||||
);
|
||||
case SftpWorkerStatus.preparing:
|
||||
return _wrapInCard(status, _s.sftpDlPrepare, trailing: loadingIcon);
|
||||
case SftpWorkerStatus.sshConnectted:
|
||||
@@ -104,10 +113,7 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
|
||||
return _wrapInCard(
|
||||
status,
|
||||
_s.unknown,
|
||||
trailing: const Icon(
|
||||
Icons.error,
|
||||
size: 40,
|
||||
),
|
||||
trailing: const Icon(Icons.error, size: 40),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
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/data/res/misc.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/stringx.dart';
|
||||
@@ -24,12 +24,11 @@ import '../../../data/provider/server.dart';
|
||||
import '../../../data/provider/sftp.dart';
|
||||
import '../../../data/res/path.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../../data/store/private_key.dart';
|
||||
import '../../../locator.dart';
|
||||
import '../../widget/fade_in.dart';
|
||||
import '../../widget/input_field.dart';
|
||||
import '../../widget/two_line_text.dart';
|
||||
import 'downloading.dart';
|
||||
import 'mission.dart';
|
||||
|
||||
class SFTPPage extends StatefulWidget {
|
||||
final ServerPrivateInfo spi;
|
||||
@@ -44,6 +43,8 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
final SftpBrowserStatus _status = SftpBrowserStatus();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
final _sftp = locator<SftpProvider>();
|
||||
|
||||
late S _s;
|
||||
|
||||
ServerState? _state;
|
||||
@@ -117,12 +118,38 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Widget _buildUploadBtn() {
|
||||
return IconButton(
|
||||
onPressed: () async {
|
||||
final path = await AppRoute(
|
||||
const SFTPDownloadedPage(
|
||||
isPickFile: true,
|
||||
final idx = await showRoundDialog(
|
||||
context: context,
|
||||
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')
|
||||
.go<String>(context);
|
||||
ListTile(
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -131,7 +158,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
showSnackBar(context, const Text('remote path is null'));
|
||||
return;
|
||||
}
|
||||
locator<SftpProvider>().add(
|
||||
_sftp.add(
|
||||
SftpReqItem(widget.spi, remotePath, path),
|
||||
SftpReqType.upload,
|
||||
);
|
||||
@@ -302,39 +329,20 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
final file = await _status.client!.open(
|
||||
_getRemotePath(name),
|
||||
mode: SftpFileOpenMode.read | SftpFileOpenMode.write,
|
||||
);
|
||||
final localPath = '${(await sftpDir).path}${_getRemotePath(name)}';
|
||||
await Directory(localPath.substring(0, localPath.lastIndexOf('/')))
|
||||
.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 remotePath = _getRemotePath(name);
|
||||
final localPath = await _getLocalPath(remotePath);
|
||||
final completer = Completer();
|
||||
final req = SftpReqItem(widget.spi, remotePath, localPath);
|
||||
_sftp.add(req, SftpReqType.download, completer: completer);
|
||||
await completer.future;
|
||||
|
||||
final result = await AppRoute(
|
||||
EditorPage(path: localPath),
|
||||
'SFTP edit',
|
||||
).go<String>(context);
|
||||
if (result != null) {
|
||||
await local.writeAsString(result);
|
||||
await file.writeBytes(result.uint8List);
|
||||
showSnackBar(context, Text(_s.saved));
|
||||
_sftp.add(req, SftpReqType.upload);
|
||||
}
|
||||
await file.close();
|
||||
}
|
||||
|
||||
void _download(BuildContext context, SftpName name) {
|
||||
@@ -351,18 +359,14 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
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(
|
||||
widget.spi,
|
||||
remotePath,
|
||||
local,
|
||||
await _getLocalPath(remotePath),
|
||||
),
|
||||
SftpReqType.download,
|
||||
key: key,
|
||||
);
|
||||
|
||||
context.pop();
|
||||
@@ -564,6 +568,10 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
return pathJoin(prePath, name.filename);
|
||||
}
|
||||
|
||||
Future<String> _getLocalPath(String remotePath) async {
|
||||
return '${(await sftpDir).path}$remotePath';
|
||||
}
|
||||
|
||||
Future<void> _listDir({String? path, SSHClient? client}) async {
|
||||
if (_status.isBusy) {
|
||||
return;
|
||||
@@ -23,7 +23,7 @@ import '../../data/res/terminal.dart';
|
||||
import '../../data/res/virtual_key.dart';
|
||||
import '../../data/store/setting.dart';
|
||||
import '../../locator.dart';
|
||||
import 'sftp/view.dart';
|
||||
import 'sftp/remote.dart';
|
||||
|
||||
class SSHPage extends StatefulWidget {
|
||||
final ServerPrivateInfo spi;
|
||||
|
||||
@@ -3,8 +3,13 @@ import 'package:flutter/material.dart';
|
||||
/// 渐隐渐显实现
|
||||
class FadeIn extends StatefulWidget {
|
||||
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
|
||||
_MyFadeInState createState() => _MyFadeInState();
|
||||
@@ -19,7 +24,7 @@ class _MyFadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 377),
|
||||
duration: widget.duration,
|
||||
);
|
||||
_animation = Tween(
|
||||
begin: 0.0,
|
||||
|
||||
@@ -475,9 +475,9 @@
|
||||
baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 341;
|
||||
CURRENT_PROJECT_VERSION = 343;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0.341;
|
||||
MARKETING_VERSION = 1.0.343;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -490,9 +490,9 @@
|
||||
baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 341;
|
||||
CURRENT_PROJECT_VERSION = 343;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0.341;
|
||||
MARKETING_VERSION = 1.0.343;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -505,9 +505,9 @@
|
||||
baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 341;
|
||||
CURRENT_PROJECT_VERSION = 343;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0.341;
|
||||
MARKETING_VERSION = 1.0.343;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
13
make.dart
13
make.dart
@@ -142,6 +142,19 @@ Future<void> flutterBuildMacOS() async {
|
||||
Future<void> flutterBuildAndroid() async {
|
||||
await flutterBuild('apk');
|
||||
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 {
|
||||
|
||||
@@ -372,14 +372,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
||||
@@ -66,7 +66,6 @@ dependencies:
|
||||
highlight: ^0.7.0
|
||||
flutter_highlight: ^0.7.0
|
||||
code_text_field: ^1.1.0
|
||||
flutter_staggered_animations: ^1.1.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_native_splash: ^2.1.6
|
||||
|
||||
Reference in New Issue
Block a user