mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
opt.: backup
This commit is contained in:
@@ -472,7 +472,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -480,7 +480,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -604,7 +604,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -612,7 +612,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -630,7 +630,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -638,7 +638,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -659,7 +659,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -672,7 +672,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||||
@@ -698,7 +698,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -711,7 +711,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -734,7 +734,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -747,7 +747,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:toolbox/view/widget/rebuild.dart';
|
||||||
|
|
||||||
|
import '../../data/model/server/snippet.dart';
|
||||||
|
import '../../data/provider/snippet.dart';
|
||||||
|
import '../../data/res/ui.dart';
|
||||||
|
import '../../locator.dart';
|
||||||
|
import '../../view/widget/input_field.dart';
|
||||||
|
import '../../view/widget/picker.dart';
|
||||||
|
import '../route.dart';
|
||||||
|
|
||||||
extension ContextX on BuildContext {
|
extension ContextX on BuildContext {
|
||||||
void pop<T extends Object?>([T? result]) {
|
void pop<T extends Object?>([T? result]) {
|
||||||
@@ -9,3 +19,124 @@ extension ContextX on BuildContext {
|
|||||||
|
|
||||||
bool get isDark => Theme.of(this).brightness == Brightness.dark;
|
bool get isDark => Theme.of(this).brightness == Brightness.dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SnackBarX on BuildContext {
|
||||||
|
void showSnackBar(String text) =>
|
||||||
|
ScaffoldMessenger.of(this).showSnackBar(SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
));
|
||||||
|
|
||||||
|
void showSnackBarWithAction(
|
||||||
|
String content,
|
||||||
|
String action,
|
||||||
|
GestureTapCallback onTap,
|
||||||
|
) {
|
||||||
|
ScaffoldMessenger.of(this).showSnackBar(SnackBar(
|
||||||
|
content: Text(content),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: action,
|
||||||
|
onPressed: onTap,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void showRestartSnackbar({String? btn, String? msg}) {
|
||||||
|
showSnackBarWithAction(
|
||||||
|
msg ?? 'Need restart to take effect',
|
||||||
|
btn ?? 'Restart',
|
||||||
|
() => RebuildWidget.restartApp(this),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DialogX on BuildContext {
|
||||||
|
Future<T?> showRoundDialog<T>({
|
||||||
|
Widget? child,
|
||||||
|
List<Widget>? actions,
|
||||||
|
Widget? title,
|
||||||
|
bool barrierDismiss = true,
|
||||||
|
}) async {
|
||||||
|
return await showDialog<T>(
|
||||||
|
context: this,
|
||||||
|
barrierDismissible: barrierDismiss,
|
||||||
|
builder: (_) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: title,
|
||||||
|
content: child,
|
||||||
|
actions: actions,
|
||||||
|
actionsPadding: const EdgeInsets.all(17),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showLoadingDialog({bool barrierDismiss = false}) {
|
||||||
|
showRoundDialog(
|
||||||
|
child: centerSizedLoading,
|
||||||
|
barrierDismiss: barrierDismiss,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> showPwdDialog(
|
||||||
|
String? user,
|
||||||
|
) async {
|
||||||
|
if (!mounted) return null;
|
||||||
|
final s = S.of(this)!;
|
||||||
|
return await showRoundDialog<String>(
|
||||||
|
title: Text(user ?? s.pwd),
|
||||||
|
child: Input(
|
||||||
|
autoFocus: true,
|
||||||
|
type: TextInputType.visiblePassword,
|
||||||
|
obscureText: true,
|
||||||
|
onSubmitted: (val) => pop(val.trim()),
|
||||||
|
label: s.pwd,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showSnippetDialog(
|
||||||
|
S s,
|
||||||
|
void Function(Snippet s) onSelected,
|
||||||
|
) {
|
||||||
|
final provider = locator<SnippetProvider>();
|
||||||
|
if (provider.snippets.isEmpty) {
|
||||||
|
showRoundDialog(
|
||||||
|
child: Text(s.noSavedSnippet),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => pop(),
|
||||||
|
child: Text(s.ok),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
pop();
|
||||||
|
AppRoute.snippetEdit().go(this);
|
||||||
|
},
|
||||||
|
child: Text(s.add),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var snippet = provider.snippets.first;
|
||||||
|
showRoundDialog(
|
||||||
|
title: Text(s.choose),
|
||||||
|
child: Picker(
|
||||||
|
items: provider.snippets.map((e) => Text(e.name)).toList(),
|
||||||
|
onSelected: (idx) => snippet = provider.snippets[idx],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
pop();
|
||||||
|
onSelected(snippet);
|
||||||
|
},
|
||||||
|
child: Text(s.ok),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import 'dart:async';
|
|||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/core/extension/stringx.dart';
|
import 'package:toolbox/core/extension/stringx.dart';
|
||||||
import 'package:toolbox/core/extension/uint8list.dart';
|
import 'package:toolbox/core/extension/uint8list.dart';
|
||||||
import 'package:toolbox/core/utils/ui.dart';
|
|
||||||
|
|
||||||
import '../../data/res/misc.dart';
|
import '../../data/res/misc.dart';
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ extension SSHClientX on SSHClient {
|
|||||||
if (data.contains('[sudo] password for ')) {
|
if (data.contains('[sudo] password for ')) {
|
||||||
final user = pwdRequestWithUserReg.firstMatch(data)?.group(1);
|
final user = pwdRequestWithUserReg.firstMatch(data)?.group(1);
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
final pwd = await showPwdDialog(context, user);
|
final pwd = await context.showPwdDialog(user);
|
||||||
if (pwd == null || pwd.isEmpty) {
|
if (pwd == null || pwd.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,15 +57,14 @@ Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
|||||||
|
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
showSnackBar(context, const Text('Null l10n'));
|
context.showSnackBar('Null l10n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final min = update.build.min.current;
|
final min = update.build.min.current;
|
||||||
|
|
||||||
if (min != null && min > BuildData.build) {
|
if (min != null && min > BuildData.build) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Text(s.updateTipTooLow(newest)),
|
child: Text(s.updateTipTooLow(newest)),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -77,8 +76,7 @@ Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showSnackBarWithAction(
|
context.showSnackBarWithAction(
|
||||||
context,
|
|
||||||
'${s.updateTip(newest)} \n${update.changelog.current}',
|
'${s.updateTip(newest)} \n${update.changelog.current}',
|
||||||
s.update,
|
s.update,
|
||||||
() => _doUpdate(update, context, s),
|
() => _doUpdate(update, context, s),
|
||||||
@@ -97,8 +95,7 @@ Future<void> _doUpdate(AppUpdate update, BuildContext context, S s) async {
|
|||||||
} else if (isMacOS) {
|
} else if (isMacOS) {
|
||||||
await openUrl(url);
|
await openUrl(url);
|
||||||
} else {
|
} else {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Text(s.platformNotSupportUpdate),
|
child: Text(s.platformNotSupportUpdate),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import '../../data/model/app/backup.dart';
|
|
||||||
import '../../data/res/path.dart';
|
|
||||||
import '../../data/store/docker.dart';
|
|
||||||
import '../../data/store/private_key.dart';
|
|
||||||
import '../../data/store/server.dart';
|
|
||||||
import '../../data/store/setting.dart';
|
|
||||||
import '../../data/store/snippet.dart';
|
|
||||||
import '../../locator.dart';
|
|
||||||
|
|
||||||
final _server = locator<ServerStore>();
|
|
||||||
final _snippet = locator<SnippetStore>();
|
|
||||||
final _privateKey = locator<PrivateKeyStore>();
|
|
||||||
final _dockerHosts = locator<DockerStore>();
|
|
||||||
final _setting = locator<SettingStore>();
|
|
||||||
|
|
||||||
Future<String> get backupPath async => '${await docDir}/srvbox_bak.json';
|
|
||||||
|
|
||||||
const backupFormatVersion = 1;
|
|
||||||
|
|
||||||
Future<void> backup() async {
|
|
||||||
final result = _diyEncrtpt(
|
|
||||||
json.encode(
|
|
||||||
Backup(
|
|
||||||
version: backupFormatVersion,
|
|
||||||
date: DateTime.now().toString().split('.').first,
|
|
||||||
spis: _server.fetch(),
|
|
||||||
snippets: _snippet.fetch(),
|
|
||||||
keys: _privateKey.fetch(),
|
|
||||||
dockerHosts: _dockerHosts.fetchAll(),
|
|
||||||
settings: _setting.toJson(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await File(await backupPath).writeAsString(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void restore(Backup backup) {
|
|
||||||
for (final s in backup.snippets) {
|
|
||||||
_snippet.put(s);
|
|
||||||
}
|
|
||||||
for (final s in backup.spis) {
|
|
||||||
_server.put(s);
|
|
||||||
}
|
|
||||||
for (final s in backup.keys) {
|
|
||||||
_privateKey.put(s);
|
|
||||||
}
|
|
||||||
for (final k in backup.dockerHosts.keys) {
|
|
||||||
final val = backup.dockerHosts[k];
|
|
||||||
if (val != null && val is String && val.isNotEmpty) {
|
|
||||||
_dockerHosts.put(k, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Backup> decodeBackup(String raw) async {
|
|
||||||
return await compute(_decode, raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
Backup _decode(String raw) {
|
|
||||||
final decrypted = _diyDecrypt(raw);
|
|
||||||
return Backup.fromJson(json.decode(decrypted));
|
|
||||||
}
|
|
||||||
|
|
||||||
String _diyEncrtpt(String raw) =>
|
|
||||||
json.encode(raw.codeUnits.map((e) => e * 2 + 1).toList(growable: false));
|
|
||||||
String _diyDecrypt(String raw) {
|
|
||||||
final list = json.decode(raw);
|
|
||||||
final sb = StringBuffer();
|
|
||||||
for (final e in list) {
|
|
||||||
sb.writeCharCode((e - 1) ~/ 2);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
@@ -100,6 +100,9 @@ class ICloud {
|
|||||||
static Future<Iterable<ICloudErr>?> sync({
|
static Future<Iterable<ICloudErr>?> sync({
|
||||||
required Iterable<String> relativePaths,
|
required Iterable<String> relativePaths,
|
||||||
}) async {
|
}) async {
|
||||||
|
final uploadFiles = <String>[];
|
||||||
|
final downloadFiles = <String>[];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final errs = <ICloudErr>[];
|
final errs = <ICloudErr>[];
|
||||||
|
|
||||||
@@ -147,11 +150,13 @@ class ICloud {
|
|||||||
|
|
||||||
/// Local is newer than remote, so upload local file
|
/// Local is newer than remote, so upload local file
|
||||||
if (remoteDate.isBefore(localDate)) {
|
if (remoteDate.isBefore(localDate)) {
|
||||||
|
await delete(relativePath);
|
||||||
final err = await upload(relativePath: relativePath);
|
final err = await upload(relativePath: relativePath);
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
errs.add(err);
|
errs.add(err);
|
||||||
}
|
}
|
||||||
//_logger.info('local newer: $relativePath');
|
//_logger.info('local newer: $relativePath');
|
||||||
|
uploadFiles.add(relativePath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +166,7 @@ class ICloud {
|
|||||||
errs.add(err);
|
errs.add(err);
|
||||||
}
|
}
|
||||||
//_logger.info('remote newer: $relativePath');
|
//_logger.info('remote newer: $relativePath');
|
||||||
|
downloadFiles.add(relativePath);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await Future.wait(mission);
|
await Future.wait(mission);
|
||||||
@@ -170,12 +176,11 @@ class ICloud {
|
|||||||
_logger.warning('Sync failed: $relativePaths', e, s);
|
_logger.warning('Sync failed: $relativePaths', e, s);
|
||||||
return [ICloudErr(type: ICloudErrType.generic, message: '$e')];
|
return [ICloudErr(type: ICloudErrType.generic, message: '$e')];
|
||||||
} finally {
|
} finally {
|
||||||
_logger.info('Sync finished.');
|
_logger.info('Sync upload: $uploadFiles, download: $downloadFiles');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncApple() async {
|
static Future<void> syncDb() async {
|
||||||
if (!isIOS && !isMacOS) return;
|
if (!isIOS && !isMacOS) return;
|
||||||
final docPath = await docDir;
|
final docPath = await docDir;
|
||||||
final dir = Directory(docPath);
|
final dir = Directory(docPath);
|
||||||
@@ -185,3 +190,4 @@ Future<void> syncApple() async {
|
|||||||
final paths = files.map((e) => e.path.replaceFirst('$docPath/', ''));
|
final paths = files.map((e) => e.path.replaceFirst('$docPath/', ''));
|
||||||
await ICloud.sync(relativePaths: paths);
|
await ICloud.sync(relativePaths: paths);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
|
|
||||||
import '../../data/provider/app.dart';
|
import '../../data/provider/app.dart';
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
import '../../view/widget/rebuild.dart';
|
|
||||||
import 'platform.dart';
|
import 'platform.dart';
|
||||||
|
|
||||||
final _app = locator<AppProvider>();
|
final _app = locator<AppProvider>();
|
||||||
@@ -64,10 +63,7 @@ String? getFileName(String? path) {
|
|||||||
return path.split('/').last;
|
return path.split('/').last;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rebuildAll(BuildContext context) {
|
/// Return fmt: 2021-01-01 00:00:00
|
||||||
RebuildWidget.restartApp(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getTime(int? unixMill) {
|
String getTime(int? unixMill) {
|
||||||
return DateTime.fromMillisecondsSinceEpoch((unixMill ?? 0) * 1000)
|
return DateTime.fromMillisecondsSinceEpoch((unixMill ?? 0) * 1000)
|
||||||
.toString()
|
.toString()
|
||||||
|
|||||||
@@ -3,125 +3,17 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:toolbox/core/extension/context.dart';
|
|
||||||
import 'package:toolbox/data/model/app/tab.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../data/model/server/snippet.dart';
|
|
||||||
import '../../data/provider/snippet.dart';
|
|
||||||
import '../../data/res/ui.dart';
|
|
||||||
import '../../locator.dart';
|
|
||||||
import '../../view/widget/input_field.dart';
|
|
||||||
import '../../view/widget/picker.dart';
|
|
||||||
import '../persistant_store.dart';
|
|
||||||
import '../route.dart';
|
|
||||||
import 'misc.dart';
|
import 'misc.dart';
|
||||||
import 'platform.dart';
|
import 'platform.dart';
|
||||||
import '../extension/stringx.dart';
|
import '../extension/stringx.dart';
|
||||||
import '../extension/uint8list.dart';
|
import '../extension/uint8list.dart';
|
||||||
|
|
||||||
void showSnackBar(BuildContext context, Widget child) =>
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: child,
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
));
|
|
||||||
|
|
||||||
void showSnackBarWithAction(
|
|
||||||
BuildContext context,
|
|
||||||
String content,
|
|
||||||
String action,
|
|
||||||
GestureTapCallback onTap,
|
|
||||||
) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text(content),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: action,
|
|
||||||
onPressed: onTap,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
void showRestartSnackbar(BuildContext context, {String? btn, String? msg}) {
|
|
||||||
showSnackBarWithAction(
|
|
||||||
context,
|
|
||||||
msg ?? 'Need restart to take effect',
|
|
||||||
btn ?? 'Restart',
|
|
||||||
() => rebuildAll(context),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> openUrl(String url) async {
|
Future<bool> openUrl(String url) async {
|
||||||
return await launchUrl(url.uri, mode: LaunchMode.externalApplication);
|
return await launchUrl(url.uri, mode: LaunchMode.externalApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> showRoundDialog<T>({
|
|
||||||
required BuildContext context,
|
|
||||||
Widget? child,
|
|
||||||
List<Widget>? actions,
|
|
||||||
Widget? title,
|
|
||||||
bool barrierDismiss = true,
|
|
||||||
}) async {
|
|
||||||
return await showDialog<T>(
|
|
||||||
context: context,
|
|
||||||
barrierDismissible: barrierDismiss,
|
|
||||||
builder: (_) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: title,
|
|
||||||
content: child,
|
|
||||||
actions: actions,
|
|
||||||
actionsPadding: const EdgeInsets.all(17),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showLoadingDialog(BuildContext context, {bool barrierDismiss = false}) {
|
|
||||||
showRoundDialog(
|
|
||||||
context: context,
|
|
||||||
child: centerSizedLoading,
|
|
||||||
barrierDismiss: barrierDismiss,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> showPwdDialog(
|
|
||||||
BuildContext context,
|
|
||||||
String? user,
|
|
||||||
) async {
|
|
||||||
if (!context.mounted) return null;
|
|
||||||
final s = S.of(context)!;
|
|
||||||
return await showRoundDialog<String>(
|
|
||||||
context: context,
|
|
||||||
title: Text(user ?? s.pwd),
|
|
||||||
child: Input(
|
|
||||||
autoFocus: true,
|
|
||||||
type: TextInputType.visiblePassword,
|
|
||||||
obscureText: true,
|
|
||||||
onSubmitted: (val) => context.pop(val.trim()),
|
|
||||||
label: s.pwd,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildSwitch(
|
|
||||||
BuildContext context,
|
|
||||||
StorePropertyBase<bool> prop, {
|
|
||||||
void Function(bool)? func,
|
|
||||||
}) {
|
|
||||||
return ValueListenableBuilder(
|
|
||||||
valueListenable: prop.listenable(),
|
|
||||||
builder: (context, bool value, widget) {
|
|
||||||
return Switch(
|
|
||||||
value: value,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (func != null) func(value);
|
|
||||||
prop.put(value);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTransparentNavigationBar(BuildContext context) {
|
void setTransparentNavigationBar(BuildContext context) {
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
@@ -133,18 +25,6 @@ void setTransparentNavigationBar(BuildContext context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String tabTitleName(BuildContext context, AppTab tab) {
|
|
||||||
final s = S.of(context)!;
|
|
||||||
switch (tab) {
|
|
||||||
case AppTab.server:
|
|
||||||
return s.server;
|
|
||||||
case AppTab.snippet:
|
|
||||||
return s.convert;
|
|
||||||
case AppTab.ping:
|
|
||||||
return 'Ping';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadFontFile(String localPath) async {
|
Future<void> loadFontFile(String localPath) async {
|
||||||
if (localPath.isEmpty) return;
|
if (localPath.isEmpty) return;
|
||||||
final name = getFileName(localPath);
|
final name = getFileName(localPath);
|
||||||
@@ -156,53 +36,6 @@ Future<void> loadFontFile(String localPath) async {
|
|||||||
await fontLoader.load();
|
await fontLoader.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showSnippetDialog(
|
|
||||||
BuildContext context,
|
|
||||||
S s,
|
|
||||||
void Function(Snippet s) onSelected,
|
|
||||||
) {
|
|
||||||
final provider = locator<SnippetProvider>();
|
|
||||||
if (provider.snippets.isEmpty) {
|
|
||||||
showRoundDialog(
|
|
||||||
context: context,
|
|
||||||
child: Text(s.noSavedSnippet),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(s.ok),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.pop();
|
|
||||||
AppRoute.snippetEdit().go(context);
|
|
||||||
},
|
|
||||||
child: Text(s.add),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var snippet = provider.snippets.first;
|
|
||||||
showRoundDialog(
|
|
||||||
context: context,
|
|
||||||
title: Text(s.choose),
|
|
||||||
child: Picker(
|
|
||||||
items: provider.snippets.map((e) => Text(e.name)).toList(),
|
|
||||||
onSelected: (idx) => snippet = provider.snippets[idx],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
context.pop();
|
|
||||||
onSelected(snippet);
|
|
||||||
},
|
|
||||||
child: Text(s.ok),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void switchStatusBar({required bool hide}) {
|
void switchStatusBar({required bool hide}) {
|
||||||
if (hide) {
|
if (hide) {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
import 'package:toolbox/data/model/server/private_key_info.dart';
|
||||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||||
import 'package:toolbox/data/model/server/snippet.dart';
|
import 'package:toolbox/data/model/server/snippet.dart';
|
||||||
|
import 'package:toolbox/data/res/path.dart';
|
||||||
|
import 'package:toolbox/data/store/docker.dart';
|
||||||
|
import 'package:toolbox/data/store/private_key.dart';
|
||||||
|
import 'package:toolbox/data/store/server.dart';
|
||||||
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
|
import 'package:toolbox/data/store/snippet.dart';
|
||||||
|
import 'package:toolbox/locator.dart';
|
||||||
|
|
||||||
|
const backupFormatVersion = 1;
|
||||||
|
|
||||||
|
final _logger = Logger('Backup');
|
||||||
|
|
||||||
class Backup {
|
class Backup {
|
||||||
// backup format version
|
// backup format version
|
||||||
@@ -45,4 +60,63 @@ class Backup {
|
|||||||
'dockerHosts': dockerHosts,
|
'dockerHosts': dockerHosts,
|
||||||
'settings': settings,
|
'settings': settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Backup.loadFromStore()
|
||||||
|
: version = backupFormatVersion,
|
||||||
|
date = DateTime.now().toString().split('.').first,
|
||||||
|
spis = _server.fetch(),
|
||||||
|
snippets = _snippet.fetch(),
|
||||||
|
keys = _privateKey.fetch(),
|
||||||
|
dockerHosts = _dockerHosts.fetchAll(),
|
||||||
|
settings = _setting.toJson();
|
||||||
|
|
||||||
|
static Future<void> backup() async {
|
||||||
|
final result = _diyEncrtpt(json.encode(Backup.loadFromStore()));
|
||||||
|
await File(await backupPath).writeAsString(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> restore() async {
|
||||||
|
for (final s in snippets) {
|
||||||
|
_snippet.put(s);
|
||||||
|
}
|
||||||
|
for (final s in spis) {
|
||||||
|
_server.put(s);
|
||||||
|
}
|
||||||
|
for (final s in keys) {
|
||||||
|
_privateKey.put(s);
|
||||||
|
}
|
||||||
|
for (final k in dockerHosts.keys) {
|
||||||
|
final val = dockerHosts[k];
|
||||||
|
if (val != null && val is String && val.isNotEmpty) {
|
||||||
|
_dockerHosts.put(k, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Backup.fromJsonString(String raw)
|
||||||
|
: this.fromJson(json.decode(_diyDecrypt(raw)));
|
||||||
|
}
|
||||||
|
|
||||||
|
final _server = locator<ServerStore>();
|
||||||
|
final _snippet = locator<SnippetStore>();
|
||||||
|
final _privateKey = locator<PrivateKeyStore>();
|
||||||
|
final _dockerHosts = locator<DockerStore>();
|
||||||
|
final _setting = locator<SettingStore>();
|
||||||
|
|
||||||
|
String _diyEncrtpt(String raw) => json.encode(
|
||||||
|
raw.codeUnits.map((e) => e * 2 + 1).toList(growable: false),
|
||||||
|
);
|
||||||
|
|
||||||
|
String _diyDecrypt(String raw) {
|
||||||
|
try {
|
||||||
|
final list = json.decode(raw);
|
||||||
|
final sb = StringBuffer();
|
||||||
|
for (final e in list) {
|
||||||
|
sb.writeCharCode((e - 1) ~/ 2);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (e, trace) {
|
||||||
|
_logger.warning('Decrypt failed', e, trace);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class PsResult {
|
|||||||
procs.add(Proc.parse(line, map));
|
procs.add(Proc.parse(line, map));
|
||||||
} catch (e, trace) {
|
} catch (e, trace) {
|
||||||
errs.add('$line: $e');
|
errs.add('$line: $e');
|
||||||
_logger.warning(trace);
|
_logger.warning('Parse process failed', e, trace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
class BuildData {
|
class BuildData {
|
||||||
static const String name = "ServerBox";
|
static const String name = "ServerBox";
|
||||||
static const int build = 545;
|
static const int build = 547;
|
||||||
static const String engine = "3.13.2";
|
static const String engine = "3.13.2";
|
||||||
static const String buildAt = "2023-09-11 23:23:58.257948";
|
static const String buildAt = "2023-09-12 14:04:07.018274";
|
||||||
static const int modifications = 6;
|
static const int modifications = 4;
|
||||||
static const int script = 14;
|
static const int script = 14;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,3 +43,5 @@ Future<String> get fontDir async {
|
|||||||
await dir.create(recursive: true);
|
await dir.create(recursive: true);
|
||||||
return _fontDir!;
|
return _fontDir!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> get backupPath async => '${await docDir}/srvbox_bak.json';
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class SettingStore extends PersistentStore {
|
|||||||
Map<String, dynamic> toJson() => {for (var e in box.keys) e: box.get(e)};
|
Map<String, dynamic> toJson() => {for (var e in box.keys) e: box.get(e)};
|
||||||
|
|
||||||
// ------BEGIN------
|
// ------BEGIN------
|
||||||
|
//
|
||||||
// These settings are not displayed in the settings page
|
// These settings are not displayed in the settings page
|
||||||
// You can edit them in the settings json editor (by long press the settings
|
// You can edit them in the settings json editor (by long press the settings
|
||||||
// item in the drawer of the home page)
|
// item in the drawer of the home page)
|
||||||
@@ -46,6 +47,25 @@ class SettingStore extends PersistentStore {
|
|||||||
'textFactor',
|
'textFactor',
|
||||||
1.0,
|
1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Lanch page idx
|
||||||
|
late final launchPage = StoreProperty(
|
||||||
|
box,
|
||||||
|
'launchPage',
|
||||||
|
defaultLaunchPageIdx,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Server detail disk ignore path
|
||||||
|
late final diskIgnorePath =
|
||||||
|
StoreListProperty(box, 'diskIgnorePath', defaultDiskIgnorePath);
|
||||||
|
|
||||||
|
/// Use double column servers page on Desktop
|
||||||
|
late final doubleColumnServersPage = StoreProperty(
|
||||||
|
box,
|
||||||
|
'doubleColumnServersPage',
|
||||||
|
isDesktop,
|
||||||
|
);
|
||||||
|
|
||||||
// ------END------
|
// ------END------
|
||||||
|
|
||||||
late final primaryColor = StoreProperty(
|
late final primaryColor = StoreProperty(
|
||||||
@@ -60,15 +80,6 @@ class SettingStore extends PersistentStore {
|
|||||||
defaultUpdateInterval,
|
defaultUpdateInterval,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Lanch page idx
|
|
||||||
late final launchPage = StoreProperty(
|
|
||||||
box,
|
|
||||||
'launchPage',
|
|
||||||
defaultLaunchPageIdx,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final termColorIdx = StoreProperty(box, 'termColorIdx', 0);
|
|
||||||
|
|
||||||
// Max retry count when connect to server
|
// Max retry count when connect to server
|
||||||
late final maxRetryCount = StoreProperty(box, 'maxRetryCount', 2);
|
late final maxRetryCount = StoreProperty(box, 'maxRetryCount', 2);
|
||||||
|
|
||||||
@@ -93,10 +104,6 @@ class SettingStore extends PersistentStore {
|
|||||||
// SSH term font size
|
// SSH term font size
|
||||||
late final termFontSize = StoreProperty(box, 'termFontSize', 13.0);
|
late final termFontSize = StoreProperty(box, 'termFontSize', 13.0);
|
||||||
|
|
||||||
// Server detail disk ignore path
|
|
||||||
late final diskIgnorePath =
|
|
||||||
StoreListProperty(box, 'diskIgnorePath', defaultDiskIgnorePath);
|
|
||||||
|
|
||||||
// Locale
|
// Locale
|
||||||
late final locale = StoreProperty<String>(box, 'locale', '');
|
late final locale = StoreProperty<String>(box, 'locale', '');
|
||||||
|
|
||||||
@@ -184,13 +191,6 @@ class SettingStore extends PersistentStore {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Use double column servers page on Desktop
|
|
||||||
late final doubleColumnServersPage = StoreProperty(
|
|
||||||
box,
|
|
||||||
'doubleColumnServersPage',
|
|
||||||
isDesktop,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Whether use system's primary color as the app's primary color
|
/// Whether use system's primary color as the app's primary color
|
||||||
late final useSystemPrimaryColor = StoreProperty(
|
late final useSystemPrimaryColor = StoreProperty(
|
||||||
box,
|
box,
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ Future<void> initApp() async {
|
|||||||
primaryColor = Color(settings.primaryColor.fetch());
|
primaryColor = Color(settings.primaryColor.fetch());
|
||||||
|
|
||||||
// Don't call it via `await`, it will block the main thread.
|
// Don't call it via `await`, it will block the main thread.
|
||||||
if (settings.icloudSync.fetch()) syncApple();
|
if (settings.icloudSync.fetch()) ICloud.syncDb();
|
||||||
|
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
// Only start service when [bgRun] is true.
|
// Only start service when [bgRun] is true.
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:toolbox/core/extension/context.dart';
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/core/utils/backup.dart';
|
|
||||||
import 'package:toolbox/core/utils/icloud.dart';
|
|
||||||
import 'package:toolbox/core/utils/platform.dart';
|
import 'package:toolbox/core/utils/platform.dart';
|
||||||
|
import 'package:toolbox/data/model/app/backup.dart';
|
||||||
|
import 'package:toolbox/data/res/path.dart';
|
||||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
|
|
||||||
import '../../core/utils/misc.dart';
|
import '../../core/utils/misc.dart';
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../../data/res/ui.dart';
|
import '../../data/res/ui.dart';
|
||||||
import '../../data/store/setting.dart';
|
import '../../data/store/setting.dart';
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
import '../widget/custom_appbar.dart';
|
import '../widget/custom_appbar.dart';
|
||||||
|
import '../widget/store_switch.dart';
|
||||||
|
|
||||||
|
final _logger = Logger('Backup');
|
||||||
|
|
||||||
class BackupPage extends StatelessWidget {
|
class BackupPage extends StatelessWidget {
|
||||||
BackupPage({Key? key}) : super(key: key);
|
BackupPage({Key? key}) : super(key: key);
|
||||||
@@ -62,7 +66,7 @@ class BackupPage extends StatelessWidget {
|
|||||||
s.backup,
|
s.backup,
|
||||||
Icons.save,
|
Icons.save,
|
||||||
() async {
|
() async {
|
||||||
await backup();
|
await Backup.backup();
|
||||||
await shareFiles(context, [await backupPath]);
|
await shareFiles(context, [await backupPath]);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -103,53 +107,47 @@ class BackupPage extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
width13,
|
width13,
|
||||||
IconButton(
|
// Hive db only save data into local file after app exit,
|
||||||
onPressed: () async {
|
// so this button is useless
|
||||||
showLoadingDialog(context);
|
// IconButton(
|
||||||
await syncApple();
|
// onPressed: () async {
|
||||||
context.pop();
|
// showLoadingDialog(context);
|
||||||
showRestartSnackbar(context, btn: s.restart, msg: s.icloudSynced);
|
// await ICloud.syncDb();
|
||||||
},
|
// context.pop();
|
||||||
icon: const Icon(Icons.sync)),
|
// showRestartSnackbar(context, btn: s.restart, msg: s.icloudSynced);
|
||||||
width13,
|
// },
|
||||||
buildSwitch(context, _setting.icloudSync)
|
// icon: const Icon(Icons.sync)),
|
||||||
|
// width13,
|
||||||
|
StoreSwitch(prop: _setting.icloudSync)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onRestore(BuildContext context, S s) async {
|
Future<void> _onRestore(BuildContext context, S s) async {
|
||||||
final path = await pickOneFile();
|
final path = await pickOneFile();
|
||||||
if (path == null) {
|
if (path == null) return;
|
||||||
showSnackBar(context, Text(s.notSelected));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
if (!file.existsSync()) {
|
if (!await file.exists()) {
|
||||||
showSnackBar(context, Text(s.fileNotExist(path)));
|
context.showSnackBar(s.fileNotExist(path));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final text = await file.readAsString();
|
final text = await file.readAsString();
|
||||||
_import(text, context, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _import(String text, BuildContext context, S s) async {
|
|
||||||
if (text.isEmpty) {
|
if (text.isEmpty) {
|
||||||
showSnackBar(context, Text(s.fieldMustNotEmpty));
|
context.showSnackBar(s.fieldMustNotEmpty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _importBackup(text, context, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _importBackup(String raw, BuildContext context, S s) async {
|
|
||||||
try {
|
try {
|
||||||
final backup = await decodeBackup(raw);
|
context.showLoadingDialog();
|
||||||
|
final backup = await compute(Backup.fromJsonString, text.trim());
|
||||||
if (backupFormatVersion != backup.version) {
|
if (backupFormatVersion != backup.version) {
|
||||||
showSnackBar(context, Text(s.backupVersionNotMatch));
|
context.showSnackBar(s.backupVersionNotMatch);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await showRoundDialog(
|
await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(s.restore),
|
title: Text(s.restore),
|
||||||
child: Text(s.restoreSureWithDate(backup.date)),
|
child: Text(s.restoreSureWithDate(backup.date)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -159,17 +157,19 @@ class BackupPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
restore(backup);
|
backup.restore();
|
||||||
context.pop();
|
context.pop();
|
||||||
showRestartSnackbar(context, btn: s.restart, msg: s.needRestart);
|
context.showRestartSnackbar(btn: s.restart, msg: s.needRestart);
|
||||||
},
|
},
|
||||||
child: Text(s.ok),
|
child: Text(s.ok),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e, trace) {
|
||||||
showSnackBar(context, Text(e.toString()));
|
_logger.warning('Import backup failed', e, trace);
|
||||||
rethrow;
|
context.showSnackBar(e.toString());
|
||||||
|
} finally {
|
||||||
|
context.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/data/res/ui.dart';
|
import 'package:toolbox/data/res/ui.dart';
|
||||||
import 'package:toolbox/view/widget/value_notifier.dart';
|
import 'package:toolbox/view/widget/value_notifier.dart';
|
||||||
|
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../widget/custom_appbar.dart';
|
import '../widget/custom_appbar.dart';
|
||||||
import '../widget/input_field.dart';
|
import '../widget/input_field.dart';
|
||||||
import '../widget/popup_menu.dart';
|
import '../widget/popup_menu.dart';
|
||||||
@@ -74,7 +74,7 @@ class _ConvertPageState extends State<ConvertPage>
|
|||||||
_textEditingControllerResult.text = doConvert();
|
_textEditingControllerResult.text = doConvert();
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar(context, Text('Error: \n$e'));
|
context.showSnackBar('Error: \n$e');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: _s.convert,
|
tooltip: _s.convert,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:toolbox/core/route.dart';
|
|||||||
import 'package:toolbox/data/model/docker/image.dart';
|
import 'package:toolbox/data/model/docker/image.dart';
|
||||||
import 'package:toolbox/view/widget/input_field.dart';
|
import 'package:toolbox/view/widget/input_field.dart';
|
||||||
|
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../../data/model/docker/ps.dart';
|
import '../../data/model/docker/ps.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import '../../data/model/server/server_private_info.dart';
|
||||||
import '../../data/provider/docker.dart';
|
import '../../data/provider/docker.dart';
|
||||||
@@ -60,7 +59,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
_docker.init(
|
_docker.init(
|
||||||
client,
|
client,
|
||||||
widget.spi.user,
|
widget.spi.user,
|
||||||
(user) async => await showPwdDialog(context, user),
|
(user) async => await context.showPwdDialog(user),
|
||||||
widget.spi.id,
|
widget.spi.id,
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
@@ -76,7 +75,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await _docker.refresh();
|
await _docker.refresh();
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
@@ -101,8 +100,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
final imageCtrl = TextEditingController();
|
final imageCtrl = TextEditingController();
|
||||||
final nameCtrl = TextEditingController();
|
final nameCtrl = TextEditingController();
|
||||||
final argsCtrl = TextEditingController();
|
final argsCtrl = TextEditingController();
|
||||||
await showRoundDialog(
|
await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.newContainer),
|
title: Text(_s.newContainer),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -151,8 +149,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showAddCmdPreview(String cmd) async {
|
Future<void> _showAddCmdPreview(String cmd) async {
|
||||||
await showRoundDialog(
|
await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.preview),
|
title: Text(_s.preview),
|
||||||
child: Text(cmd),
|
child: Text(cmd),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -163,11 +160,11 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
final result = await _docker.run(cmd);
|
final result = await _docker.run(cmd);
|
||||||
context.pop();
|
context.pop();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
showSnackBar(context, Text(result.message ?? _s.unknownError));
|
context.showSnackBar(result.message ?? _s.unknownError);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(_s.run),
|
child: Text(_s.run),
|
||||||
@@ -261,8 +258,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showImageRmDialog(DockerImage e) {
|
void _showImageRmDialog(DockerImage e) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureDelete(e.repo)),
|
child: Text(_s.sureDelete(e.repo)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -277,10 +273,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
'docker rmi ${e.id} -f',
|
'docker rmi ${e.id} -f',
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
showSnackBar(
|
context.showSnackBar(result.message ?? _s.unknownError);
|
||||||
context,
|
|
||||||
Text(result.message ?? _s.unknownError),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(_s.ok, style: textRed),
|
child: Text(_s.ok, style: textRed),
|
||||||
@@ -382,15 +375,14 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
onSelected: (DockerMenuType item) async {
|
onSelected: (DockerMenuType item) async {
|
||||||
switch (item) {
|
switch (item) {
|
||||||
case DockerMenuType.rm:
|
case DockerMenuType.rm:
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureDelete(dItem.name)),
|
child: Text(_s.sureDelete(dItem.name)),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await _docker.delete(dItem.containerId);
|
await _docker.delete(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
@@ -400,17 +392,17 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case DockerMenuType.start:
|
case DockerMenuType.start:
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await _docker.start(dItem.containerId);
|
await _docker.start(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
break;
|
break;
|
||||||
case DockerMenuType.stop:
|
case DockerMenuType.stop:
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await _docker.stop(dItem.containerId);
|
await _docker.stop(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
break;
|
break;
|
||||||
case DockerMenuType.restart:
|
case DockerMenuType.restart:
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await _docker.restart(dItem.containerId);
|
await _docker.restart(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
break;
|
break;
|
||||||
@@ -484,8 +476,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
final id = widget.spi.id;
|
final id = widget.spi.id;
|
||||||
final host = _store.fetch(id) ?? 'unix:///run/user/1000/docker.sock';
|
final host = _store.fetch(id) ?? 'unix:///run/user/1000/docker.sock';
|
||||||
final ctrl = TextEditingController(text: host);
|
final ctrl = TextEditingController(text: host);
|
||||||
await showRoundDialog(
|
await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.dockerEditHost),
|
title: Text(_s.dockerEditHost),
|
||||||
child: Input(
|
child: Input(
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:flutter_highlight/themes/a11y-light.dart';
|
|||||||
import 'package:flutter_highlight/themes/monokai.dart';
|
import 'package:flutter_highlight/themes/monokai.dart';
|
||||||
import 'package:toolbox/core/extension/context.dart';
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/core/utils/misc.dart';
|
import 'package:toolbox/core/utils/misc.dart';
|
||||||
import 'package:toolbox/core/utils/ui.dart';
|
|
||||||
import 'package:toolbox/data/res/highlight.dart';
|
import 'package:toolbox/data/res/highlight.dart';
|
||||||
import 'package:toolbox/data/store/setting.dart';
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
@@ -107,7 +106,7 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
// If path is not null, then it's a file editor
|
// If path is not null, then it's a file editor
|
||||||
// save the text and return true to pop the page
|
// save the text and return true to pop the page
|
||||||
if (widget.path != null) {
|
if (widget.path != null) {
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await File(widget.path!).writeAsString(_controller.text);
|
await File(widget.path!).writeAsString(_controller.text);
|
||||||
context.pop();
|
context.pop();
|
||||||
context.pop(true);
|
context.pop(true);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:after_layout/after_layout.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
|
|
||||||
import '../../core/analysis.dart';
|
import '../../core/analysis.dart';
|
||||||
import '../../core/route.dart';
|
import '../../core/route.dart';
|
||||||
@@ -205,8 +206,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
children: [
|
children: [
|
||||||
_buildIcon(),
|
_buildIcon(),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => showRoundDialog(
|
onPressed: () => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_versionStr),
|
title: Text(_versionStr),
|
||||||
child: const Text(BuildData.buildAt),
|
child: const Text(BuildData.buildAt),
|
||||||
),
|
),
|
||||||
@@ -265,8 +265,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showAboutDialog() {
|
void _showAboutDialog() {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.about),
|
title: Text(_s.about),
|
||||||
child: _buildAboutContent(),
|
child: _buildAboutContent(),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -374,8 +373,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
final newSettings = json.decode(result) as Map<String, dynamic>;
|
final newSettings = json.decode(result) as Map<String, dynamic>;
|
||||||
_setting.box.putAll(newSettings);
|
_setting.box.putAll(newSettings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: Text('${_s.save}:\n$e'),
|
child: Text('${_s.save}:\n$e'),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:toolbox/core/utils/misc.dart';
|
|||||||
import 'package:toolbox/view/widget/value_notifier.dart';
|
import 'package:toolbox/view/widget/value_notifier.dart';
|
||||||
|
|
||||||
import '../../core/extension/uint8list.dart';
|
import '../../core/extension/uint8list.dart';
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../../data/model/server/ping_result.dart';
|
import '../../data/model/server/ping_result.dart';
|
||||||
import '../../data/provider/server.dart';
|
import '../../data/provider/server.dart';
|
||||||
import '../../data/res/color.dart';
|
import '../../data/res/color.dart';
|
||||||
@@ -71,8 +70,7 @@ class _PingPageState extends State<PingPage>
|
|||||||
return FloatingActionButton(
|
return FloatingActionButton(
|
||||||
heroTag: 'ping',
|
heroTag: 'ping',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.choose),
|
title: Text(_s.choose),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
@@ -95,8 +93,7 @@ class _PingPageState extends State<PingPage>
|
|||||||
try {
|
try {
|
||||||
await doPing();
|
await doPing();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: Text(e.toString()),
|
child: Text(e.toString()),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -173,18 +170,18 @@ class _PingPageState extends State<PingPage>
|
|||||||
_results.value.clear();
|
_results.value.clear();
|
||||||
final target = _textEditingController.text.trim();
|
final target = _textEditingController.text.trim();
|
||||||
if (target.isEmpty) {
|
if (target.isEmpty) {
|
||||||
showSnackBar(context, Text(_s.pingInputIP));
|
context.showSnackBar(_s.pingInputIP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_serverProvider.servers.isEmpty) {
|
if (_serverProvider.servers.isEmpty) {
|
||||||
showSnackBar(context, Text(_s.pingNoServer));
|
context.showSnackBar(_s.pingNoServer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// avoid ping command injection
|
/// avoid ping command injection
|
||||||
if (!targetReg.hasMatch(target)) {
|
if (!targetReg.hasMatch(target)) {
|
||||||
showSnackBar(context, Text(_s.pingInputIP));
|
context.showSnackBar(_s.pingInputIP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import 'package:toolbox/data/res/misc.dart';
|
|||||||
import 'package:toolbox/view/widget/input_field.dart';
|
import 'package:toolbox/view/widget/input_field.dart';
|
||||||
|
|
||||||
import '../../../core/utils/server.dart';
|
import '../../../core/utils/server.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/server/private_key_info.dart';
|
import '../../../data/model/server/private_key_info.dart';
|
||||||
import '../../../data/provider/private_key.dart';
|
import '../../../data/provider/private_key.dart';
|
||||||
import '../../../data/res/ui.dart';
|
import '../../../data/res/ui.dart';
|
||||||
@@ -92,8 +91,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
tooltip: _s.delete,
|
tooltip: _s.delete,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureDelete(widget.pki!.id)),
|
child: Text(_s.sureDelete(widget.pki!.id)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -128,7 +126,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
|||||||
final key = _keyController.text.trim();
|
final key = _keyController.text.trim();
|
||||||
final pwd = _pwdController.text;
|
final pwd = _pwdController.text;
|
||||||
if (name.isEmpty || key.isEmpty) {
|
if (name.isEmpty || key.isEmpty) {
|
||||||
showSnackBar(context, Text(_s.fieldMustNotEmpty));
|
context.showSnackBar(_s.fieldMustNotEmpty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
@@ -144,7 +142,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
|||||||
_provider.add(pki);
|
_provider.add(pki);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar(context, Text(e.toString()));
|
context.showSnackBar(e.toString());
|
||||||
rethrow;
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -184,26 +182,23 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final path = await pickOneFile();
|
final path = await pickOneFile();
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
showSnackBar(context, Text(_s.fieldMustNotEmpty));
|
context.showSnackBar(_s.fieldMustNotEmpty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
if (!file.existsSync()) {
|
if (!file.existsSync()) {
|
||||||
showSnackBar(context, Text(_s.fileNotExist(path)));
|
context.showSnackBar(_s.fileNotExist(path));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final size = (await file.stat()).size;
|
final size = (await file.stat()).size;
|
||||||
if (size > privateKeyMaxSize) {
|
if (size > privateKeyMaxSize) {
|
||||||
showSnackBar(
|
context.showSnackBar(
|
||||||
context,
|
|
||||||
Text(
|
|
||||||
_s.fileTooLarge(
|
_s.fileTooLarge(
|
||||||
path,
|
path,
|
||||||
size.convertBytes,
|
size.convertBytes,
|
||||||
privateKeyMaxSize.convertBytes,
|
privateKeyMaxSize.convertBytes,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:toolbox/core/extension/context.dart';
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/core/utils/ui.dart';
|
|
||||||
import 'package:toolbox/data/store/private_key.dart';
|
import 'package:toolbox/data/store/private_key.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
|
|
||||||
@@ -94,8 +93,7 @@ class _PrivateKeyListState extends State<PrivateKeysListPage>
|
|||||||
id: 'system',
|
id: 'system',
|
||||||
key: idRsaFile.readAsStringSync(),
|
key: idRsaFile.readAsStringSync(),
|
||||||
);
|
);
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.addSystemPrivateKeyTip),
|
child: Text(_s.addSystemPrivateKeyTip),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:toolbox/core/extension/context.dart';
|
|||||||
import 'package:toolbox/core/extension/uint8list.dart';
|
import 'package:toolbox/core/extension/uint8list.dart';
|
||||||
import 'package:toolbox/core/utils/misc.dart';
|
import 'package:toolbox/core/utils/misc.dart';
|
||||||
|
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../../data/model/app/shell_func.dart';
|
import '../../data/model/app/shell_func.dart';
|
||||||
import '../../data/model/server/proc.dart';
|
import '../../data/model/server/proc.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import '../../data/model/server/server_private_info.dart';
|
||||||
@@ -67,7 +66,7 @@ class _ProcessPageState extends State<ProcessPage> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
final result = await _client?.run(AppShellFuncType.process.exec).string;
|
final result = await _client?.run(AppShellFuncType.process.exec).string;
|
||||||
if (result == null || result.isEmpty) {
|
if (result == null || result.isEmpty) {
|
||||||
showSnackBar(context, Text(_s.noResult));
|
context.showSnackBar(_s.noResult);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_result = PsResult.parse(result, sort: _procSortMode);
|
_result = PsResult.parse(result, sort: _procSortMode);
|
||||||
@@ -113,8 +112,7 @@ class _ProcessPageState extends State<ProcessPage> {
|
|||||||
if (_result.error != null) {
|
if (_result.error != null) {
|
||||||
actions.add(IconButton(
|
actions.add(IconButton(
|
||||||
icon: const Icon(Icons.error),
|
icon: const Icon(Icons.error),
|
||||||
onPressed: () => showRoundDialog(
|
onPressed: () => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: SingleChildScrollView(child: Text(_result.error!)),
|
child: SingleChildScrollView(child: Text(_result.error!)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -166,8 +164,7 @@ class _ProcessPageState extends State<ProcessPage> {
|
|||||||
trailing: _buildItemTrail(proc),
|
trailing: _buildItemTrail(proc),
|
||||||
onTap: () => _lastFocusId = proc.pid,
|
onTap: () => _lastFocusId = proc.pid,
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureStop(proc.pid)),
|
child: Text(_s.sureStop(proc.pid)),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:toolbox/core/extension/context.dart';
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
|
|
||||||
import '../../../core/route.dart';
|
import '../../../core/route.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/server/private_key_info.dart';
|
import '../../../data/model/server/private_key_info.dart';
|
||||||
import '../../../data/model/server/server_private_info.dart';
|
import '../../../data/model/server/server_private_info.dart';
|
||||||
import '../../../data/provider/private_key.dart';
|
import '../../../data/provider/private_key.dart';
|
||||||
@@ -114,8 +113,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
PreferredSizeWidget _buildAppBar() {
|
PreferredSizeWidget _buildAppBar() {
|
||||||
final delBtn = IconButton(
|
final delBtn = IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureToDeleteServer(widget.spi!.name)),
|
child: Text(_s.sureToDeleteServer(widget.spi!.name)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -323,12 +321,11 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
|
|
||||||
void _onSave() async {
|
void _onSave() async {
|
||||||
if (_ipController.text == '') {
|
if (_ipController.text == '') {
|
||||||
showSnackBar(context, Text(_s.plzEnterHost));
|
context.showSnackBar(_s.plzEnterHost);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_keyIdx.value == null && _passwordController.text == '') {
|
if (_keyIdx.value == null && _passwordController.text == '') {
|
||||||
final cancel = await showRoundDialog<bool>(
|
final cancel = await context.showRoundDialog<bool>(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureNoPwd),
|
child: Text(_s.sureNoPwd),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -348,7 +345,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
}
|
}
|
||||||
// If [_pubKeyIndex] is -1, it means that the user has not selected
|
// If [_pubKeyIndex] is -1, it means that the user has not selected
|
||||||
if (_keyIdx.value == -1) {
|
if (_keyIdx.value == -1) {
|
||||||
showSnackBar(context, Text(_s.plzSelectKey));
|
context.showSnackBar(_s.plzSelectKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_usernameController.text.isEmpty) {
|
if (_usernameController.text.isEmpty) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/core/extension/media_queryx.dart';
|
import 'package:toolbox/core/extension/media_queryx.dart';
|
||||||
import 'package:toolbox/core/extension/ssh_client.dart';
|
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||||
import 'package:toolbox/data/model/app/shell_func.dart';
|
import 'package:toolbox/data/model/app/shell_func.dart';
|
||||||
@@ -11,7 +12,6 @@ import 'package:toolbox/data/model/app/shell_func.dart';
|
|||||||
import '../../../core/route.dart';
|
import '../../../core/route.dart';
|
||||||
import '../../../core/utils/misc.dart';
|
import '../../../core/utils/misc.dart';
|
||||||
import '../../../core/utils/platform.dart';
|
import '../../../core/utils/platform.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/app/net_view.dart';
|
import '../../../data/model/app/net_view.dart';
|
||||||
import '../../../data/model/server/disk.dart';
|
import '../../../data/model/server/disk.dart';
|
||||||
import '../../../data/model/server/server.dart';
|
import '../../../data/model/server/server.dart';
|
||||||
@@ -376,8 +376,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showFailReason(ServerStatus ss) {
|
void _showFailReason(ServerStatus ss) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Text(ss.failedInfo ?? _s.unknownError),
|
child: Text(ss.failedInfo ?? _s.unknownError),
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import '../../../core/route.dart';
|
|||||||
import '../../../core/utils/misc.dart';
|
import '../../../core/utils/misc.dart';
|
||||||
import '../../../core/utils/platform.dart';
|
import '../../../core/utils/platform.dart';
|
||||||
import '../../../core/update.dart';
|
import '../../../core/update.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/app/net_view.dart';
|
import '../../../data/model/app/net_view.dart';
|
||||||
import '../../../data/provider/app.dart';
|
import '../../../data/provider/app.dart';
|
||||||
import '../../../data/provider/server.dart';
|
import '../../../data/provider/server.dart';
|
||||||
@@ -32,6 +31,7 @@ import '../../widget/custom_appbar.dart';
|
|||||||
import '../../widget/future_widget.dart';
|
import '../../widget/future_widget.dart';
|
||||||
import '../../widget/input_field.dart';
|
import '../../widget/input_field.dart';
|
||||||
import '../../widget/round_rect_card.dart';
|
import '../../widget/round_rect_card.dart';
|
||||||
|
import '../../widget/store_switch.dart';
|
||||||
import '../../widget/value_notifier.dart';
|
import '../../widget/value_notifier.dart';
|
||||||
|
|
||||||
class SettingPage extends StatefulWidget {
|
class SettingPage extends StatefulWidget {
|
||||||
@@ -43,7 +43,6 @@ class SettingPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _SettingPageState extends State<SettingPage> {
|
class _SettingPageState extends State<SettingPage> {
|
||||||
final _themeKey = GlobalKey<PopupMenuButtonState<int>>();
|
final _themeKey = GlobalKey<PopupMenuButtonState<int>>();
|
||||||
//final _startPageKey = GlobalKey<PopupMenuButtonState<int>>();
|
|
||||||
final _updateIntervalKey = GlobalKey<PopupMenuButtonState<int>>();
|
final _updateIntervalKey = GlobalKey<PopupMenuButtonState<int>>();
|
||||||
final _maxRetryKey = GlobalKey<PopupMenuButtonState<int>>();
|
final _maxRetryKey = GlobalKey<PopupMenuButtonState<int>>();
|
||||||
final _localeKey = GlobalKey<PopupMenuButtonState<String>>();
|
final _localeKey = GlobalKey<PopupMenuButtonState<String>>();
|
||||||
@@ -59,7 +58,6 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
late SharedPreferences _sp;
|
late SharedPreferences _sp;
|
||||||
|
|
||||||
final _selectedColorValue = ValueNotifier(0);
|
final _selectedColorValue = ValueNotifier(0);
|
||||||
final _launchPageIdx = ValueNotifier(0);
|
|
||||||
final _nightMode = ValueNotifier(0);
|
final _nightMode = ValueNotifier(0);
|
||||||
final _maxRetryCount = ValueNotifier(0);
|
final _maxRetryCount = ValueNotifier(0);
|
||||||
final _updateInterval = ValueNotifier(0);
|
final _updateInterval = ValueNotifier(0);
|
||||||
@@ -91,7 +89,6 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_serverProvider = locator<ServerProvider>();
|
_serverProvider = locator<ServerProvider>();
|
||||||
_setting = locator<SettingStore>();
|
_setting = locator<SettingStore>();
|
||||||
_launchPageIdx.value = _setting.launchPage.fetch();
|
|
||||||
_nightMode.value = _setting.themeMode.fetch();
|
_nightMode.value = _setting.themeMode.fetch();
|
||||||
_updateInterval.value = _setting.serverStatusUpdateInterval.fetch();
|
_updateInterval.value = _setting.serverStatusUpdateInterval.fetch();
|
||||||
_maxRetryCount.value = _setting.maxRetryCount.fetch();
|
_maxRetryCount.value = _setting.maxRetryCount.fetch();
|
||||||
@@ -113,8 +110,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
title: Text(_s.setting),
|
title: Text(_s.setting),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => showRoundDialog(
|
onPressed: () => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureDelete(_s.all)),
|
child: Text(_s.sureDelete(_s.all)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -122,7 +118,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
_setting.box.deleteAll(_setting.box.keys);
|
_setting.box.deleteAll(_setting.box.keys);
|
||||||
context.pop();
|
context.pop();
|
||||||
showSnackBar(context, Text(_s.success));
|
context.showSnackBar(_s.success);
|
||||||
},
|
},
|
||||||
child: Text(_s.ok, style: const TextStyle(color: Colors.red)),
|
child: Text(_s.ok, style: const TextStyle(color: Colors.red)),
|
||||||
),
|
),
|
||||||
@@ -203,9 +199,9 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
_buildNetViewType(),
|
_buildNetViewType(),
|
||||||
_buildUpdateInterval(),
|
_buildUpdateInterval(),
|
||||||
_buildMaxRetry(),
|
_buildMaxRetry(),
|
||||||
_buildDiskIgnorePath(),
|
//_buildDiskIgnorePath(),
|
||||||
_buildDeleteServers(),
|
_buildDeleteServers(),
|
||||||
if (isDesktop) _buildDoubleColumnServersPage(),
|
//if (isDesktop) _buildDoubleColumnServersPage(),
|
||||||
].map((e) => RoundRectCard(e)).toList(),
|
].map((e) => RoundRectCard(e)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -251,7 +247,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
title: Text(_s.autoCheckUpdate),
|
title: Text(_s.autoCheckUpdate),
|
||||||
subtitle: Text(display, style: grey),
|
subtitle: Text(display, style: grey),
|
||||||
onTap: () => doUpdate(ctx, force: true),
|
onTap: () => doUpdate(ctx, force: true),
|
||||||
trailing: buildSwitch(context, _setting.autoCheckAppUpdate),
|
trailing: StoreSwitch(prop: _setting.autoCheckAppUpdate),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -289,7 +285,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
_setting.serverStatusUpdateInterval.put(val);
|
_setting.serverStatusUpdateInterval.put(val);
|
||||||
_serverProvider.startAutoRefresh();
|
_serverProvider.startAutoRefresh();
|
||||||
if (val == 0) {
|
if (val == 0) {
|
||||||
showSnackBar(context, Text(_s.updateIntervalEqual0));
|
context.showSnackBar(_s.updateIntervalEqual0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -313,8 +309,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
title: Text(_s.primaryColorSeed),
|
title: Text(_s.primaryColorSeed),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final ctrl = TextEditingController(text: primaryColor.toHex);
|
final ctrl = TextEditingController(text: primaryColor.toHex);
|
||||||
await showRoundDialog(
|
await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.primaryColorSeed),
|
title: Text(_s.primaryColorSeed),
|
||||||
child: StatefulBuilder(builder: (context, setState) {
|
child: StatefulBuilder(builder: (context, setState) {
|
||||||
final children = <Widget>[
|
final children = <Widget>[
|
||||||
@@ -322,9 +317,8 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
if (!isIOS)
|
if (!isIOS)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(_s.followSystem),
|
title: Text(_s.followSystem),
|
||||||
trailing: buildSwitch(
|
trailing: StoreSwitch(
|
||||||
context,
|
prop: _setting.useSystemPrimaryColor,
|
||||||
_setting.useSystemPrimaryColor,
|
|
||||||
func: (_) => setState(() {}),
|
func: (_) => setState(() {}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -361,14 +355,14 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
void _onSaveColor(String s) {
|
void _onSaveColor(String s) {
|
||||||
final color = s.hexToColor;
|
final color = s.hexToColor;
|
||||||
if (color == null) {
|
if (color == null) {
|
||||||
showSnackBar(context, Text(_s.failed));
|
context.showSnackBar(_s.failed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_selectedColorValue.value = color.value;
|
_selectedColorValue.value = color.value;
|
||||||
_setting.primaryColor.put(_selectedColorValue.value);
|
_setting.primaryColor.put(_selectedColorValue.value);
|
||||||
primaryColor = color;
|
primaryColor = color;
|
||||||
context.pop();
|
context.pop();
|
||||||
showRestartSnackbar(context, btn: _s.restart, msg: _s.needRestart);
|
context.showRestartSnackbar(btn: _s.restart, msg: _s.needRestart);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget _buildLaunchPage() {
|
// Widget _buildLaunchPage() {
|
||||||
@@ -515,9 +509,9 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_pushToken.value != null) {
|
if (_pushToken.value != null) {
|
||||||
copy2Clipboard(_pushToken.value!);
|
copy2Clipboard(_pushToken.value!);
|
||||||
showSnackBar(context, Text(_s.success));
|
context.showSnackBar(_s.success);
|
||||||
} else {
|
} else {
|
||||||
showSnackBar(context, Text(_s.getPushTokenFailed));
|
context.showSnackBar(_s.getPushTokenFailed);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -548,8 +542,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
style: textSize15,
|
style: textSize15,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.font),
|
title: Text(_s.font),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -560,8 +553,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
_setting.fontPath.delete();
|
_setting.fontPath.delete();
|
||||||
context.pop();
|
context.pop();
|
||||||
showRestartSnackbar(
|
context.showRestartSnackbar(
|
||||||
context,
|
|
||||||
btn: _s.restart,
|
btn: _s.restart,
|
||||||
msg: _s.needRestart,
|
msg: _s.needRestart,
|
||||||
);
|
);
|
||||||
@@ -588,16 +580,16 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.pop();
|
context.pop();
|
||||||
showRestartSnackbar(context, btn: _s.restart, msg: _s.needRestart);
|
context.showRestartSnackbar(btn: _s.restart, msg: _s.needRestart);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSnackBar(context, Text(_s.failed));
|
context.showSnackBar(_s.failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBgRun() {
|
Widget _buildBgRun() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.bgRun),
|
title: Text(_s.bgRun),
|
||||||
trailing: buildSwitch(context, _setting.bgRun),
|
trailing: StoreSwitch(prop: _setting.bgRun),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,42 +607,42 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDiskIgnorePath() {
|
// Widget _buildDiskIgnorePath() {
|
||||||
final paths = _setting.diskIgnorePath.fetch();
|
// final paths = _setting.diskIgnorePath.fetch();
|
||||||
return ListTile(
|
// return ListTile(
|
||||||
title: Text(_s.diskIgnorePath),
|
// title: Text(_s.diskIgnorePath),
|
||||||
trailing: Text(_s.edit, style: textSize15),
|
// trailing: Text(_s.edit, style: textSize15),
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
final ctrller = TextEditingController(text: json.encode(paths));
|
// final ctrller = TextEditingController(text: json.encode(paths));
|
||||||
void onSubmit() {
|
// void onSubmit() {
|
||||||
try {
|
// try {
|
||||||
final list = List<String>.from(json.decode(ctrller.text));
|
// final list = List<String>.from(json.decode(ctrller.text));
|
||||||
_setting.diskIgnorePath.put(list);
|
// _setting.diskIgnorePath.put(list);
|
||||||
context.pop();
|
// context.pop();
|
||||||
showSnackBar(context, Text(_s.success));
|
// showSnackBar(context, Text(_s.success));
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
showSnackBar(context, Text(e.toString()));
|
// showSnackBar(context, Text(e.toString()));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
showRoundDialog(
|
// showRoundDialog(
|
||||||
context: context,
|
// context: context,
|
||||||
title: Text(_s.diskIgnorePath),
|
// title: Text(_s.diskIgnorePath),
|
||||||
child: Input(
|
// child: Input(
|
||||||
autoFocus: true,
|
// autoFocus: true,
|
||||||
controller: ctrller,
|
// controller: ctrller,
|
||||||
label: 'JSON',
|
// label: 'JSON',
|
||||||
type: TextInputType.visiblePassword,
|
// type: TextInputType.visiblePassword,
|
||||||
maxLines: 3,
|
// maxLines: 3,
|
||||||
onSubmitted: (_) => onSubmit(),
|
// onSubmitted: (_) => onSubmit(),
|
||||||
),
|
// ),
|
||||||
actions: [
|
// actions: [
|
||||||
TextButton(onPressed: onSubmit, child: Text(_s.ok)),
|
// TextButton(onPressed: onSubmit, child: Text(_s.ok)),
|
||||||
],
|
// ],
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _buildLocale() {
|
Widget _buildLocale() {
|
||||||
final items = S.supportedLocales
|
final items = S.supportedLocales
|
||||||
@@ -675,7 +667,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onSelected: (String idx) {
|
onSelected: (String idx) {
|
||||||
_localeCode.value = idx;
|
_localeCode.value = idx;
|
||||||
_setting.locale.put(idx);
|
_setting.locale.put(idx);
|
||||||
showRestartSnackbar(context, btn: _s.restart, msg: _s.needRestart);
|
context.showRestartSnackbar(btn: _s.restart, msg: _s.needRestart);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
_s.languageName,
|
_s.languageName,
|
||||||
@@ -690,7 +682,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.sshVirtualKeyAutoOff),
|
title: Text(_s.sshVirtualKeyAutoOff),
|
||||||
subtitle: const Text('Ctrl & Alt', style: grey),
|
subtitle: const Text('Ctrl & Alt', style: grey),
|
||||||
trailing: buildSwitch(context, _setting.sshVirtualKeyAutoOff),
|
trailing: StoreSwitch(prop: _setting.sshVirtualKeyAutoOff),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,11 +755,9 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
Widget _buildFullScreenSwitch() {
|
Widget _buildFullScreenSwitch() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.fullScreen),
|
title: Text(_s.fullScreen),
|
||||||
trailing: buildSwitch(
|
trailing: StoreSwitch(
|
||||||
context,
|
prop: _setting.fullScreen,
|
||||||
_setting.fullScreen,
|
func: (_) => context.showRestartSnackbar(
|
||||||
func: (_) => showRestartSnackbar(
|
|
||||||
context,
|
|
||||||
btn: _s.restart,
|
btn: _s.restart,
|
||||||
msg: _s.needRestart,
|
msg: _s.needRestart,
|
||||||
),
|
),
|
||||||
@@ -779,7 +769,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.fullScreenJitter),
|
title: Text(_s.fullScreenJitter),
|
||||||
subtitle: Text(_s.fullScreenJitterHelp, style: grey),
|
subtitle: Text(_s.fullScreenJitterHelp, style: grey),
|
||||||
trailing: buildSwitch(context, _setting.fullScreenJitter),
|
trailing: StoreSwitch(prop: _setting.fullScreenJitter),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,9 +876,9 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
map.forEach((key, value) {
|
map.forEach((key, value) {
|
||||||
_sp.setString(key, value);
|
_sp.setString(key, value);
|
||||||
});
|
});
|
||||||
showSnackBar(context, Text(_s.success));
|
context.showSnackBar(_s.success);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar(context, Text(e.toString()));
|
context.showSnackBar(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,8 +895,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
final ctrl = TextEditingController(text: json.encode(data));
|
final ctrl = TextEditingController(text: json.encode(data));
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.homeWidgetUrlConfig),
|
title: Text(_s.homeWidgetUrlConfig),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
@@ -964,7 +953,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.autoUpdateHomeWidget),
|
title: Text(_s.autoUpdateHomeWidget),
|
||||||
subtitle: Text(_s.whenOpenApp, style: grey),
|
subtitle: Text(_s.whenOpenApp, style: grey),
|
||||||
trailing: buildSwitch(context, _setting.autoUpdateHomeWidget),
|
trailing: StoreSwitch(prop: _setting.autoUpdateHomeWidget),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,8 +964,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
final all = locator<ServerStore>().box.keys.map(
|
final all = locator<ServerStore>().box.keys.map(
|
||||||
(e) => TextButton(
|
(e) => TextButton(
|
||||||
onPressed: () => showRoundDialog(
|
onPressed: () => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureDelete(e)),
|
child: Text(_s.sureDelete(e)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -989,8 +977,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
child: Text(e),
|
child: Text(e),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
showRoundDialog<List<String>>(
|
context.showRoundDialog<List<String>>(
|
||||||
context: context,
|
|
||||||
title: Text(_s.choose),
|
title: Text(_s.choose),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1007,7 +994,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.moveOutServerFuncBtns),
|
title: Text(_s.moveOutServerFuncBtns),
|
||||||
subtitle: Text(_s.moveOutServerFuncBtnsHelp, style: textSize13Grey),
|
subtitle: Text(_s.moveOutServerFuncBtnsHelp, style: textSize13Grey),
|
||||||
trailing: buildSwitch(context, _setting.moveOutServerTabFuncBtns),
|
trailing: StoreSwitch(prop: _setting.moveOutServerTabFuncBtns),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1051,8 +1038,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
context.pop();
|
context.pop();
|
||||||
final fontSize = double.tryParse(ctrller.text);
|
final fontSize = double.tryParse(ctrller.text);
|
||||||
if (fontSize == null) {
|
if (fontSize == null) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.failed),
|
title: Text(_s.failed),
|
||||||
child: Text('Parsed failed: ${ctrller.text}'),
|
child: Text('Parsed failed: ${ctrller.text}'),
|
||||||
);
|
);
|
||||||
@@ -1062,8 +1048,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
property.put(fontSize);
|
property.put(fontSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.fontSize),
|
title: Text(_s.fontSize),
|
||||||
child: Input(
|
child: Input(
|
||||||
controller: ctrller,
|
controller: ctrller,
|
||||||
@@ -1085,14 +1070,14 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.sftpRmrfDir),
|
title: Text(_s.sftpRmrfDir),
|
||||||
subtitle: Text(_s.sftpRmrfDirSummary, style: grey),
|
subtitle: Text(_s.sftpRmrfDirSummary, style: grey),
|
||||||
trailing: buildSwitch(context, _setting.sftpRmrfDir),
|
trailing: StoreSwitch(prop: _setting.sftpRmrfDir),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDoubleColumnServersPage() {
|
// Widget _buildDoubleColumnServersPage() {
|
||||||
return ListTile(
|
// return ListTile(
|
||||||
title: Text(_s.doubleColumnMode),
|
// title: Text(_s.doubleColumnMode),
|
||||||
trailing: buildSwitch(context, _setting.doubleColumnServersPage),
|
// trailing: StoreSwitch(prop: _setting.doubleColumnServersPage),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/core/extension/order.dart';
|
import 'package:toolbox/core/extension/order.dart';
|
||||||
import 'package:toolbox/core/utils/platform.dart';
|
import 'package:toolbox/core/utils/platform.dart';
|
||||||
import 'package:toolbox/core/utils/ui.dart';
|
|
||||||
import 'package:toolbox/data/model/ssh/virtual_key.dart';
|
import 'package:toolbox/data/model/ssh/virtual_key.dart';
|
||||||
import 'package:toolbox/data/res/ui.dart';
|
import 'package:toolbox/data/res/ui.dart';
|
||||||
import 'package:toolbox/data/store/setting.dart';
|
import 'package:toolbox/data/store/setting.dart';
|
||||||
@@ -64,7 +64,7 @@ class _SSHVirtKeySettingPageState extends State<SSHVirtKeySettingPage> {
|
|||||||
itemCount: allKeys.length,
|
itemCount: allKeys.length,
|
||||||
onReorder: (o, n) {
|
onReorder: (o, n) {
|
||||||
if (o >= keys.length || n >= keys.length) {
|
if (o >= keys.length || n >= keys.length) {
|
||||||
showSnackBar(context, Text(_s.disabled));
|
context.showSnackBar(_s.disabled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
keys.moveByItem(keys, o, n, property: _setting.sshVirtKeys);
|
keys.moveByItem(keys, o, n, property: _setting.sshVirtKeys);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||||||
import 'package:toolbox/core/extension/context.dart';
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/view/widget/input_field.dart';
|
import 'package:toolbox/view/widget/input_field.dart';
|
||||||
|
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/server/snippet.dart';
|
import '../../../data/model/server/snippet.dart';
|
||||||
import '../../../data/provider/snippet.dart';
|
import '../../../data/provider/snippet.dart';
|
||||||
import '../../../data/res/ui.dart';
|
import '../../../data/res/ui.dart';
|
||||||
@@ -89,7 +88,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
final name = _nameController.text;
|
final name = _nameController.text;
|
||||||
final script = _scriptController.text;
|
final script = _scriptController.text;
|
||||||
if (name.isEmpty || script.isEmpty) {
|
if (name.isEmpty || script.isEmpty) {
|
||||||
showSnackBar(context, Text(_s.fieldMustNotEmpty));
|
context.showSnackBar(_s.fieldMustNotEmpty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final note = _noteController.text;
|
final note = _noteController.text;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:toolbox/core/extension/context.dart';
|
||||||
import 'package:toolbox/core/extension/order.dart';
|
import 'package:toolbox/core/extension/order.dart';
|
||||||
|
|
||||||
import '../../../core/utils/misc.dart';
|
import '../../../core/utils/misc.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/server/server.dart';
|
import '../../../data/model/server/server.dart';
|
||||||
import '../../../data/model/server/snippet.dart';
|
import '../../../data/model/server/snippet.dart';
|
||||||
import '../../../data/provider/server.dart';
|
import '../../../data/provider/server.dart';
|
||||||
@@ -151,8 +151,7 @@ class _SnippetListPageState extends State<SnippetListPage> {
|
|||||||
ids,
|
ids,
|
||||||
results,
|
results,
|
||||||
).entries.map((e) => '${e.key}:\n${e.value}').join('\n');
|
).entries.map((e) => '${e.key}:\n${e.value}').join('\n');
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.result),
|
title: Text(_s.result),
|
||||||
child: Text(result),
|
child: Text(result),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import 'package:xterm/xterm.dart';
|
|||||||
import '../../core/route.dart';
|
import '../../core/route.dart';
|
||||||
import '../../core/utils/platform.dart';
|
import '../../core/utils/platform.dart';
|
||||||
import '../../core/utils/misc.dart';
|
import '../../core/utils/misc.dart';
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../../core/utils/server.dart';
|
import '../../core/utils/server.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import '../../data/model/server/server_private_info.dart';
|
||||||
import '../../data/model/ssh/virtual_key.dart';
|
import '../../data/model/ssh/virtual_key.dart';
|
||||||
@@ -251,7 +250,7 @@ class _SSHPageState extends State<SSHPage> {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case VirtualKeyFunc.snippet:
|
case VirtualKeyFunc.snippet:
|
||||||
showSnippetDialog(context, _s, (s) {
|
context.showSnippetDialog(_s, (s) {
|
||||||
_terminal.textInput(s.script);
|
_terminal.textInput(s.script);
|
||||||
_terminal.keyInput(TerminalKey.enter);
|
_terminal.keyInput(TerminalKey.enter);
|
||||||
});
|
});
|
||||||
@@ -267,8 +266,7 @@ class _SSHPageState extends State<SSHPage> {
|
|||||||
final idx = cmds.lastIndexWhere((e) => e.toString().contains(echoPWD));
|
final idx = cmds.lastIndexWhere((e) => e.toString().contains(echoPWD));
|
||||||
final initPath = cmds[idx + 1].toString();
|
final initPath = cmds[idx + 1].toString();
|
||||||
if (initPath.isEmpty || !initPath.startsWith('/')) {
|
if (initPath.isEmpty || !initPath.startsWith('/')) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: const Text('Failed to get current path'),
|
child: const Text('Failed to get current path'),
|
||||||
);
|
);
|
||||||
@@ -342,7 +340,7 @@ class _SSHPageState extends State<SSHPage> {
|
|||||||
_setupDiscontinuityTimer();
|
_setupDiscontinuityTimer();
|
||||||
|
|
||||||
if (_session == null) {
|
if (_session == null) {
|
||||||
showSnackBar(context, const Text('Null session'));
|
context.showSnackBar('Null session');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,8 +398,7 @@ class _SSHPageState extends State<SSHPage> {
|
|||||||
_discontinuityTimer?.cancel();
|
_discontinuityTimer?.cancel();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_write('\n\nConnection lost\r\n');
|
_write('\n\nConnection lost\r\n');
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text('${_s.disconnected}\n${_s.goBackQ}'),
|
child: Text('${_s.disconnected}\n${_s.goBackQ}'),
|
||||||
barrierDismiss: false,
|
barrierDismiss: false,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import '../../../core/extension/numx.dart';
|
|||||||
import '../../../core/extension/stringx.dart';
|
import '../../../core/extension/stringx.dart';
|
||||||
import '../../../core/route.dart';
|
import '../../../core/route.dart';
|
||||||
import '../../../core/utils/misc.dart';
|
import '../../../core/utils/misc.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/app/path_with_prefix.dart';
|
import '../../../data/model/app/path_with_prefix.dart';
|
||||||
import '../../../data/res/path.dart';
|
import '../../../data/res/path.dart';
|
||||||
import '../../../data/res/ui.dart';
|
import '../../../data/res/ui.dart';
|
||||||
@@ -191,8 +190,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showDirActionDialog(FileSystemEntity file) async {
|
Future<void> _showDirActionDialog(FileSystemEntity file) async {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -220,8 +218,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
Future<void> _showFileActionDialog(FileSystemEntity file) async {
|
Future<void> _showFileActionDialog(FileSystemEntity file) async {
|
||||||
final fileName = file.path.split('/').last;
|
final fileName = file.path.split('/').last;
|
||||||
if (widget.isPickFile) {
|
if (widget.isPickFile) {
|
||||||
await showRoundDialog(
|
await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.pickFile),
|
title: Text(_s.pickFile),
|
||||||
child: Text(fileName),
|
child: Text(fileName),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -235,8 +232,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
]);
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -247,8 +243,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
context.pop();
|
context.pop();
|
||||||
final stat = await file.stat();
|
final stat = await file.stat();
|
||||||
if (stat.size > editorMaxSize) {
|
if (stat.size > editorMaxSize) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.fileTooLarge(fileName, stat.size, '1m')),
|
child: Text(_s.fileTooLarge(fileName, stat.size, '1m')),
|
||||||
);
|
);
|
||||||
@@ -260,7 +255,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
final f = File(file.absolute.path);
|
final f = File(file.absolute.path);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
f.writeAsString(result);
|
f.writeAsString(result);
|
||||||
showSnackBar(context, Text(_s.saved));
|
context.showSnackBar(_s.saved);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -289,8 +284,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
final serverProvider = locator<ServerProvider>();
|
final serverProvider = locator<ServerProvider>();
|
||||||
final ids = serverProvider.serverOrder;
|
final ids = serverProvider.serverOrder;
|
||||||
var idx = 0;
|
var idx = 0;
|
||||||
await showRoundDialog(
|
await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.server),
|
title: Text(_s.server),
|
||||||
child: Picker(
|
child: Picker(
|
||||||
items: ids.map((e) => Text(e)).toList(),
|
items: ids.map((e) => Text(e)).toList(),
|
||||||
@@ -319,7 +313,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
file.absolute.path,
|
file.absolute.path,
|
||||||
SftpReqType.upload,
|
SftpReqType.upload,
|
||||||
));
|
));
|
||||||
showSnackBar(context, Text(_s.added2List));
|
context.showSnackBar(_s.added2List);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -336,8 +330,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
|
|
||||||
void _showRenameDialog(FileSystemEntity file) {
|
void _showRenameDialog(FileSystemEntity file) {
|
||||||
final fileName = file.path.split('/').last;
|
final fileName = file.path.split('/').last;
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.rename),
|
title: Text(_s.rename),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
@@ -348,7 +341,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
try {
|
try {
|
||||||
file.renameSync(newPath);
|
file.renameSync(newPath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar(context, Text('${_s.failed}:\n$e'));
|
context.showSnackBar('${_s.failed}:\n$e');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,8 +353,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
|
|
||||||
void _showDeleteDialog(FileSystemEntity file) {
|
void _showDeleteDialog(FileSystemEntity file) {
|
||||||
final fileName = file.path.split('/').last;
|
final fileName = file.path.split('/').last;
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.delete),
|
title: Text(_s.delete),
|
||||||
child: Text(_s.sureDelete(fileName)),
|
child: Text(_s.sureDelete(fileName)),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -375,7 +367,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
try {
|
try {
|
||||||
file.deleteSync(recursive: true);
|
file.deleteSync(recursive: true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showSnackBar(context, Text('${_s.failed}:\n$e'));
|
context.showSnackBar('${_s.failed}:\n$e');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import '../../../core/extension/stringx.dart';
|
|||||||
import '../../../core/route.dart';
|
import '../../../core/route.dart';
|
||||||
import '../../../core/utils/misc.dart';
|
import '../../../core/utils/misc.dart';
|
||||||
import '../../../core/utils/platform.dart';
|
import '../../../core/utils/platform.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/server/server_private_info.dart';
|
import '../../../data/model/server/server_private_info.dart';
|
||||||
import '../../../data/model/sftp/absolute_path.dart';
|
import '../../../data/model/sftp/absolute_path.dart';
|
||||||
import '../../../data/model/sftp/browser_status.dart';
|
import '../../../data/model/sftp/browser_status.dart';
|
||||||
@@ -153,8 +152,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
Widget _buildUploadBtn() {
|
Widget _buildUploadBtn() {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final idx = await showRoundDialog(
|
final idx = await context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -186,7 +184,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
}
|
}
|
||||||
final remotePath = _status.path?.path;
|
final remotePath = _status.path?.path;
|
||||||
if (remotePath == null) {
|
if (remotePath == null) {
|
||||||
showSnackBar(context, const Text('remote path is null'));
|
context.showSnackBar('remote path is null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_sftp.add(
|
_sftp.add(
|
||||||
@@ -203,8 +201,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
|
|
||||||
Widget _buildAddBtn() {
|
Widget _buildAddBtn() {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: (() => showRoundDialog(
|
onPressed: (() => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -227,8 +224,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
return IconButton(
|
return IconButton(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final p = await showRoundDialog<String>(
|
final p = await context.showRoundDialog<String>(
|
||||||
context: context,
|
|
||||||
title: Text(_s.goto),
|
title: Text(_s.goto),
|
||||||
child: Autocomplete<String>(
|
child: Autocomplete<String>(
|
||||||
optionsBuilder: (val) {
|
optionsBuilder: (val) {
|
||||||
@@ -358,8 +354,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: children,
|
children: children,
|
||||||
@@ -370,13 +365,11 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
Future<void> _edit(BuildContext context, SftpName name) async {
|
Future<void> _edit(BuildContext context, SftpName name) async {
|
||||||
final size = name.attr.size;
|
final size = name.attr.size;
|
||||||
if (size == null || size > editorMaxSize) {
|
if (size == null || size > editorMaxSize) {
|
||||||
showSnackBar(
|
context.showSnackBar(_s.fileTooLarge(
|
||||||
context,
|
|
||||||
Text(_s.fileTooLarge(
|
|
||||||
name.filename,
|
name.filename,
|
||||||
size ?? 0,
|
size ?? 0,
|
||||||
editorMaxSize,
|
editorMaxSize,
|
||||||
)));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.pop();
|
context.pop();
|
||||||
@@ -391,20 +384,19 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
SftpReqType.download,
|
SftpReqType.download,
|
||||||
);
|
);
|
||||||
_sftp.add(req, completer: completer);
|
_sftp.add(req, completer: completer);
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await completer.future;
|
await completer.future;
|
||||||
context.pop();
|
context.pop();
|
||||||
|
|
||||||
final result = await AppRoute.editor(path: localPath).go<bool>(context);
|
final result = await AppRoute.editor(path: localPath).go<bool>(context);
|
||||||
if (result != null && result) {
|
if (result != null && result) {
|
||||||
_sftp.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
|
_sftp.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
|
||||||
showSnackBar(context, Text(_s.added2List));
|
context.showSnackBar(_s.added2List);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _download(BuildContext context, SftpName name) {
|
void _download(BuildContext context, SftpName name) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text('${_s.dl2Local(name.filename)}\n${_s.keepForeground}'),
|
child: Text('${_s.dl2Local(name.filename)}\n${_s.keepForeground}'),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -441,8 +433,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
final dirText = (isDir && !useRmrf) ? '\n${_s.sureDirEmpty}' : '';
|
final dirText = (isDir && !useRmrf) ? '\n${_s.sureDirEmpty}' : '';
|
||||||
final text = '${_s.sureDelete(file.filename)}$dirText';
|
final text = '${_s.sureDelete(file.filename)}$dirText';
|
||||||
final child = Text(text);
|
final child = Text(text);
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: child,
|
child: child,
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -453,7 +444,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
final remotePath = _getRemotePath(file);
|
final remotePath = _getRemotePath(file);
|
||||||
try {
|
try {
|
||||||
if (useRmrf) {
|
if (useRmrf) {
|
||||||
@@ -466,8 +457,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
context.pop();
|
context.pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.pop();
|
context.pop();
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: Text(e.toString()),
|
child: Text(e.toString()),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -490,8 +480,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
void _mkdir(BuildContext context) {
|
void _mkdir(BuildContext context) {
|
||||||
context.pop();
|
context.pop();
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.createFolder),
|
title: Text(_s.createFolder),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
@@ -507,8 +496,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (textController.text == '') {
|
if (textController.text == '') {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
child: Text(_s.fieldMustNotEmpty),
|
child: Text(_s.fieldMustNotEmpty),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -533,8 +521,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
void _newFile(BuildContext context) {
|
void _newFile(BuildContext context) {
|
||||||
context.pop();
|
context.pop();
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.createFile),
|
title: Text(_s.createFile),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
@@ -546,8 +533,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (textController.text == '') {
|
if (textController.text == '') {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.fieldMustNotEmpty),
|
child: Text(_s.fieldMustNotEmpty),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -561,7 +547,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
}
|
}
|
||||||
context.pop();
|
context.pop();
|
||||||
final path = '${_status.path!.path}/${textController.text}';
|
final path = '${_status.path!.path}/${textController.text}';
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await _client!.run('touch "$path"');
|
await _client!.run('touch "$path"');
|
||||||
context.pop();
|
context.pop();
|
||||||
_listDir();
|
_listDir();
|
||||||
@@ -575,8 +561,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
void _rename(BuildContext context, SftpName file) {
|
void _rename(BuildContext context, SftpName file) {
|
||||||
context.pop();
|
context.pop();
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.rename),
|
title: Text(_s.rename),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
@@ -589,8 +574,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (textController.text == '') {
|
if (textController.text == '') {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.fieldMustNotEmpty),
|
child: Text(_s.fieldMustNotEmpty),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -617,8 +601,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
final absPath = _getRemotePath(name);
|
final absPath = _getRemotePath(name);
|
||||||
final cmd = _getDecompressCmd(absPath);
|
final cmd = _getDecompressCmd(absPath);
|
||||||
if (cmd == null) {
|
if (cmd == null) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: Text('Unsupport file: ${name.filename}'),
|
child: Text('Unsupport file: ${name.filename}'),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -630,7 +613,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
await _client?.run(cmd);
|
await _client?.run(cmd);
|
||||||
context.pop();
|
context.pop();
|
||||||
_listDir();
|
_listDir();
|
||||||
@@ -647,7 +630,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
|
|
||||||
/// Only return true if the path is changed
|
/// Only return true if the path is changed
|
||||||
Future<bool> _listDir({String? path, SSHClient? client}) async {
|
Future<bool> _listDir({String? path, SSHClient? client}) async {
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
final sftpc = await client.sftp();
|
final sftpc = await client.sftp();
|
||||||
_status.client = sftpc;
|
_status.client = sftpc;
|
||||||
@@ -681,12 +664,11 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
return false;
|
return false;
|
||||||
} catch (e, trace) {
|
} catch (e, trace) {
|
||||||
context.pop();
|
context.pop();
|
||||||
_logger.warning('list dir failed', e, trace);
|
_logger.warning('List dir failed', e, trace);
|
||||||
await _backward();
|
await _backward();
|
||||||
Future.delayed(
|
Future.delayed(
|
||||||
const Duration(milliseconds: 177),
|
const Duration(milliseconds: 177),
|
||||||
() => showRoundDialog(
|
() => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: Text(e.toString()),
|
child: Text(e.toString()),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:toolbox/locator.dart';
|
|||||||
|
|
||||||
import '../../../core/extension/numx.dart';
|
import '../../../core/extension/numx.dart';
|
||||||
import '../../../core/utils/misc.dart';
|
import '../../../core/utils/misc.dart';
|
||||||
import '../../../core/utils/ui.dart';
|
|
||||||
import '../../../data/model/sftp/req.dart';
|
import '../../../data/model/sftp/req.dart';
|
||||||
import '../../../data/provider/sftp.dart';
|
import '../../../data/provider/sftp.dart';
|
||||||
import '../../../data/res/ui.dart';
|
import '../../../data/res/ui.dart';
|
||||||
@@ -111,8 +110,7 @@ class _SftpMissionPageState extends State<SftpMissionPage> {
|
|||||||
status: status,
|
status: status,
|
||||||
subtitle: _s.unknown,
|
subtitle: _s.unknown,
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () => showRoundDialog(
|
onPressed: () => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
title: Text(_s.error),
|
||||||
child: Text((status.error ?? _s.unknown).toString()),
|
child: Text((status.error ?? _s.unknown).toString()),
|
||||||
),
|
),
|
||||||
@@ -149,8 +147,7 @@ class _SftpMissionPageState extends State<SftpMissionPage> {
|
|||||||
|
|
||||||
Widget _buildDelete(String name, int id) {
|
Widget _buildDelete(String name, int id) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () => showRoundDialog(
|
onPressed: () => context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(_s.attention),
|
title: Text(_s.attention),
|
||||||
child: Text(_s.sureDelete(name)),
|
child: Text(_s.sureDelete(name)),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import '../../core/route.dart';
|
|||||||
import '../../core/utils/misc.dart';
|
import '../../core/utils/misc.dart';
|
||||||
import '../../core/utils/platform.dart';
|
import '../../core/utils/platform.dart';
|
||||||
import '../../core/utils/server.dart';
|
import '../../core/utils/server.dart';
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../../data/model/app/menu.dart';
|
import '../../data/model/app/menu.dart';
|
||||||
import '../../data/model/pkg/upgrade_info.dart';
|
import '../../data/model/pkg/upgrade_info.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import '../../data/model/server/server_private_info.dart';
|
||||||
@@ -115,8 +114,7 @@ void _onTapMoreBtns(
|
|||||||
final result =
|
final result =
|
||||||
await locator<ServerProvider>().runSnippets(spi.id, snippets);
|
await locator<ServerProvider>().runSnippets(spi.id, snippets);
|
||||||
if (result != null && result.isNotEmpty) {
|
if (result != null && result.isNotEmpty) {
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(s.result),
|
title: Text(s.result),
|
||||||
child: Text(result),
|
child: Text(result),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -186,7 +184,7 @@ Future<void> _gotoSSH(
|
|||||||
await Process.start("x-terminal-emulator", ["-e"] + sshCommand);
|
await Process.start("x-terminal-emulator", ["-e"] + sshCommand);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
showSnackBar(context, Text('Mismatch system: $system'));
|
context.showSnackBar('Mismatch system: $system');
|
||||||
}
|
}
|
||||||
// For security reason, delete the private key file after use
|
// For security reason, delete the private key file after use
|
||||||
if (shouldGenKey) {
|
if (shouldGenKey) {
|
||||||
@@ -198,7 +196,7 @@ Future<void> _gotoSSH(
|
|||||||
bool _checkClient(BuildContext context, String id, String msg) {
|
bool _checkClient(BuildContext context, String id, String msg) {
|
||||||
final server = locator<ServerProvider>().servers[id];
|
final server = locator<ServerProvider>().servers[id];
|
||||||
if (server == null || server.client == null) {
|
if (server == null || server.client == null) {
|
||||||
showSnackBar(context, Text(msg));
|
context.showSnackBar(msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -207,14 +205,14 @@ bool _checkClient(BuildContext context, String id, String msg) {
|
|||||||
Future<void> _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async {
|
Future<void> _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async {
|
||||||
final server = locator<ServerProvider>().servers[spi.id];
|
final server = locator<ServerProvider>().servers[spi.id];
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
showSnackBar(context, Text(s.noClient));
|
context.showSnackBar(s.noClient);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final sys = server.status.sysVer;
|
final sys = server.status.sysVer;
|
||||||
final pkg = PkgManager.fromDist(sys.dist);
|
final pkg = PkgManager.fromDist(sys.dist);
|
||||||
|
|
||||||
// Update pkg list
|
// Update pkg list
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
final updateCmd = pkg?.update;
|
final updateCmd = pkg?.update;
|
||||||
if (updateCmd != null) {
|
if (updateCmd != null) {
|
||||||
await server.client!.execWithPwd(
|
await server.client!.execWithPwd(
|
||||||
@@ -226,22 +224,22 @@ Future<void> _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async {
|
|||||||
|
|
||||||
final listCmd = pkg?.listUpdate;
|
final listCmd = pkg?.listUpdate;
|
||||||
if (listCmd == null) {
|
if (listCmd == null) {
|
||||||
showSnackBar(context, Text('Unsupported dist: $sys'));
|
context.showSnackBar('Unsupported dist: $sys');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get upgrade list
|
// Get upgrade list
|
||||||
showLoadingDialog(context);
|
context.showLoadingDialog();
|
||||||
final result = await server.client?.run(listCmd).string;
|
final result = await server.client?.run(listCmd).string;
|
||||||
context.pop();
|
context.pop();
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
showSnackBar(context, Text(s.noResult));
|
context.showSnackBar(s.noResult);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final list = pkg?.updateListRemoveUnused(result.split('\n'));
|
final list = pkg?.updateListRemoveUnused(result.split('\n'));
|
||||||
final upgradeable = list?.map((e) => UpgradePkgInfo(e, pkg)).toList();
|
final upgradeable = list?.map((e) => UpgradePkgInfo(e, pkg)).toList();
|
||||||
if (upgradeable == null || upgradeable.isEmpty) {
|
if (upgradeable == null || upgradeable.isEmpty) {
|
||||||
showSnackBar(context, Text(s.noUpdateAvailable));
|
context.showSnackBar(s.noUpdateAvailable);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final args = upgradeable.map((e) => e.package).join(' ');
|
final args = upgradeable.map((e) => e.package).join(' ');
|
||||||
@@ -249,8 +247,7 @@ Future<void> _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async {
|
|||||||
final upgradeCmd = isSU ? pkg?.upgrade(args) : 'sudo ${pkg?.upgrade(args)}';
|
final upgradeCmd = isSU ? pkg?.upgrade(args) : 'sudo ${pkg?.upgrade(args)}';
|
||||||
|
|
||||||
// Confirm upgrade
|
// Confirm upgrade
|
||||||
final gotoUpgrade = await showRoundDialog<bool>(
|
final gotoUpgrade = await context.showRoundDialog<bool>(
|
||||||
context: context,
|
|
||||||
title: Text(s.attention),
|
title: Text(s.attention),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Text('${s.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'),
|
child: Text('${s.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'),
|
||||||
|
|||||||
26
lib/view/widget/store_switch.dart
Normal file
26
lib/view/widget/store_switch.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../core/persistant_store.dart';
|
||||||
|
|
||||||
|
class StoreSwitch extends StatelessWidget {
|
||||||
|
final StorePropertyBase<bool> prop;
|
||||||
|
final void Function(bool)? func;
|
||||||
|
|
||||||
|
const StoreSwitch({super.key, required this.prop, this.func});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: prop.listenable(),
|
||||||
|
builder: (context, bool value, widget) {
|
||||||
|
return Switch(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
func?.call(value);
|
||||||
|
prop.put(value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import 'package:toolbox/view/widget/input_field.dart';
|
|||||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
import '../../core/utils/ui.dart';
|
|
||||||
import '../../data/model/app/tag_pickable.dart';
|
import '../../data/model/app/tag_pickable.dart';
|
||||||
import '../../data/res/color.dart';
|
import '../../data/res/color.dart';
|
||||||
|
|
||||||
@@ -133,8 +132,7 @@ class _TagEditorState extends State<TagEditor> {
|
|||||||
|
|
||||||
void _showAddTagDialog() {
|
void _showAddTagDialog() {
|
||||||
final textEditingController = TextEditingController();
|
final textEditingController = TextEditingController();
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(widget.s.add),
|
title: Text(widget.s.add),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
@@ -158,8 +156,7 @@ class _TagEditorState extends State<TagEditor> {
|
|||||||
|
|
||||||
void _showRenameDialog(String tag) {
|
void _showRenameDialog(String tag) {
|
||||||
final textEditingController = TextEditingController(text: tag);
|
final textEditingController = TextEditingController(text: tag);
|
||||||
showRoundDialog(
|
context.showRoundDialog(
|
||||||
context: context,
|
|
||||||
title: Text(widget.s.rename),
|
title: Text(widget.s.rename),
|
||||||
child: Input(
|
child: Input(
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
|
|||||||
@@ -476,9 +476,9 @@
|
|||||||
baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */;
|
baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -491,9 +491,9 @@
|
|||||||
baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */;
|
baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -506,9 +506,9 @@
|
|||||||
baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */;
|
baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 545;
|
CURRENT_PROJECT_VERSION = 547;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0.545;
|
MARKETING_VERSION = 1.0.547;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user