From 8c25b5e60bb1c16f10801eb3dc23c2c7a5823c26 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Fri, 2 Jun 2023 21:51:39 +0800 Subject: [PATCH] #33 new: upload file to sftp from `file picker` --- .dart_tool/flutter_gen/gen_l10n/l10n.dart | 30 ++++- .dart_tool/flutter_gen/gen_l10n/l10n_de.dart | 15 ++- .dart_tool/flutter_gen/gen_l10n/l10n_en.dart | 15 ++- .dart_tool/flutter_gen/gen_l10n/l10n_zh.dart | 30 ++++- ios/Runner.xcodeproj/project.pbxproj | 12 +- lib/data/model/sftp/req.dart | 7 +- lib/data/model/sftp/worker.dart | 4 +- lib/data/provider/sftp.dart | 6 +- lib/data/res/build_data.dart | 6 +- lib/l10n/app_de.arb | 5 +- lib/l10n/app_en.arb | 5 +- lib/l10n/app_zh.arb | 5 +- lib/l10n/app_zh_tw.arb | 5 +- lib/view/page/home.dart | 2 +- lib/view/page/server/tab.dart | 116 ++++++++---------- .../page/sftp/{downloaded.dart => local.dart} | 2 +- .../sftp/{downloading.dart => mission.dart} | 30 +++-- lib/view/page/sftp/{view.dart => remote.dart} | 92 +++++++------- lib/view/page/ssh.dart | 2 +- lib/view/widget/fade_in.dart | 9 +- macos/Runner.xcodeproj/project.pbxproj | 12 +- make.dart | 13 ++ pubspec.lock | 8 -- pubspec.yaml | 1 - 24 files changed, 258 insertions(+), 174 deletions(-) rename lib/view/page/sftp/{downloaded.dart => local.dart} (99%) rename lib/view/page/sftp/{downloading.dart => mission.dart} (80%) rename lib/view/page/sftp/{view.dart => remote.dart} (90%) diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index bbb64771..adc9b83d 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -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: diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart index 9d83dd47..62db6430 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart @@ -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'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index 0d951e5c..470a6be2 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -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'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index 36164e9c..aeb07020 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -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 => '标签'; diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 03b87ef9..2b3a9f4d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -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"; diff --git a/lib/data/model/sftp/req.dart b/lib/data/model/sftp/req.dart index f37618e3..86652f12 100644 --- a/lib/data/model/sftp/req.dart +++ b/lib/data/model/sftp/req.dart @@ -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: diff --git a/lib/data/model/sftp/worker.dart b/lib/data/model/sftp/worker.dart index d0799ce4..9bc09bfc 100644 --- a/lib/data/model/sftp/worker.dart +++ b/lib/data/model/sftp/worker.dart @@ -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 diff --git a/lib/data/provider/sftp.dart b/lib/data/provider/sftp.dart index b4ebacdc..a2113d66 100644 --- a/lib/data/provider/sftp.dart +++ b/lib/data/provider/sftp.dart @@ -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, )); } } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index df164339..140c4bd0 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -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; } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 5c5eb322..b936a6cb 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -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", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 099c53a7..a918a8a1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -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", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index c39cd1e7..9d7d770c 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -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": "主题", diff --git a/lib/l10n/app_zh_tw.arb b/lib/l10n/app_zh_tw.arb index eb55017b..9e8a982d 100644 --- a/lib/l10n/app_zh_tw.arb +++ b/lib/l10n/app_zh_tw.arb @@ -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": "主題", diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index a89be683..7d33fff1 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -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 { diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 78f2cfe4..2a7ef6ee 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -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 ); } - Widget _buildTagsSwitcher(ServerProvider pro) { - if (pro.tags.isEmpty) return placeholder; - final items = [null, ...pro.tags]; + Widget _buildBody() { + return RefreshIndicator( + onRefresh: () async => + await _serverProvider.refreshData(onlyFailed: true), + child: Consumer( + 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 tags) { + if (tags.isEmpty) return placeholder; + final items = [null, ...tags]; return Container( height: 37, width: _media.size.width, @@ -125,51 +168,6 @@ class _ServerPageState extends State ); } - Widget _buildBody() { - return RefreshIndicator( - onRefresh: () async => - await _serverProvider.refreshData(onlyFailed: true), - child: Consumer( - 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 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), + ), + ), ); } diff --git a/lib/view/page/sftp/downloaded.dart b/lib/view/page/sftp/local.dart similarity index 99% rename from lib/view/page/sftp/downloaded.dart rename to lib/view/page/sftp/local.dart index 24f14711..fed7c894 100644 --- a/lib/view/page/sftp/downloaded.dart +++ b/lib/view/page/sftp/local.dart @@ -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; diff --git a/lib/view/page/sftp/downloading.dart b/lib/view/page/sftp/mission.dart similarity index 80% rename from lib/view/page/sftp/downloading.dart rename to lib/view/page/sftp/mission.dart index cfbee264..1025821c 100644 --- a/lib/view/page/sftp/downloading.dart +++ b/lib/view/page/sftp/mission.dart @@ -31,7 +31,7 @@ class _SFTPDownloadingPageState extends State { return Scaffold( appBar: AppBar( title: Text( - _s.download, + _s.mission, style: textSize18, ), ), @@ -61,7 +61,11 @@ class _SFTPDownloadingPageState extends State { {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 { 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 { return _wrapInCard( status, _s.unknown, - trailing: const Icon( - Icons.error, - size: 40, - ), + trailing: const Icon(Icons.error, size: 40), ); } } diff --git a/lib/view/page/sftp/view.dart b/lib/view/page/sftp/remote.dart similarity index 90% rename from lib/view/page/sftp/view.dart rename to lib/view/page/sftp/remote.dart index 1f5d7329..31571ab2 100644 --- a/lib/view/page/sftp/view.dart +++ b/lib/view/page/sftp/remote.dart @@ -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 { final SftpBrowserStatus _status = SftpBrowserStatus(); final ScrollController _scrollController = ScrollController(); + final _sftp = locator(); + late S _s; ServerState? _state; @@ -117,12 +118,38 @@ class _SFTPPageState extends State { 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(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(context); + case 1: + return await pickOneFile(); + default: + return null; + } + }(); if (path == null) { return; } @@ -131,7 +158,7 @@ class _SFTPPageState extends State { showSnackBar(context, const Text('remote path is null')); return; } - locator().add( + _sftp.add( SftpReqItem(widget.spi, remotePath, path), SftpReqType.upload, ); @@ -302,39 +329,20 @@ class _SFTPPageState extends State { 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(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 { onPressed: () async { context.pop(); final remotePath = _getRemotePath(name); - final local = '${(await sftpDir).path}$remotePath'; - final pubKeyId = widget.spi.pubKeyId; - final key = locator().get(pubKeyId)?.privateKey; - locator().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 { return pathJoin(prePath, name.filename); } + Future _getLocalPath(String remotePath) async { + return '${(await sftpDir).path}$remotePath'; + } + Future _listDir({String? path, SSHClient? client}) async { if (_status.isBusy) { return; diff --git a/lib/view/page/ssh.dart b/lib/view/page/ssh.dart index 6263ac40..ddf5b4ca 100644 --- a/lib/view/page/ssh.dart +++ b/lib/view/page/ssh.dart @@ -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; diff --git a/lib/view/widget/fade_in.dart b/lib/view/widget/fade_in.dart index 8556acf7..88e43bb3 100644 --- a/lib/view/widget/fade_in.dart +++ b/lib/view/widget/fade_in.dart @@ -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 with SingleTickerProviderStateMixin { super.initState(); _controller = AnimationController( vsync: this, - duration: const Duration(milliseconds: 377), + duration: widget.duration, ); _animation = Tween( begin: 0.0, diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 1a090a78..a00d69a7 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -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; diff --git a/make.dart b/make.dart index 707b701f..37a0ff1c 100755 --- a/make.dart +++ b/make.dart @@ -142,6 +142,19 @@ Future flutterBuildMacOS() async { Future flutterBuildAndroid() async { await flutterBuild('apk'); await killJava(); + await scp2CDN(); +} + +Future 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 changeAppleVersion() async { diff --git a/pubspec.lock b/pubspec.lock index a8177db2..ec4a60a1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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 diff --git a/pubspec.yaml b/pubspec.yaml index cbf5db22..f355a719 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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