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