opt.: backup

This commit is contained in:
lollipopkit
2023-09-13 13:05:19 +08:00
parent 9ce7138d9b
commit 269c2a0a10
37 changed files with 535 additions and 632 deletions

View File

@@ -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)";

View File

@@ -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),
)
],
);
}
}

View File

@@ -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;
} }

View File

@@ -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(

View File

@@ -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();
}

View File

@@ -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,18 +176,18 @@ 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);
final files = await dir.list().toList(); final files = await dir.list().toList();
// filter out non-hive(db) files // filter out non-hive(db) files
files.removeWhere((e) => !e.path.endsWith('.hive')); files.removeWhere((e) => !e.path.endsWith('.hive'));
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);
}
} }

View File

@@ -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()

View File

@@ -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,

View File

@@ -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;
}
} }

View File

@@ -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);
} }
} }

View File

@@ -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;
} }

View File

@@ -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';

View File

@@ -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,

View File

@@ -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.

View File

@@ -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();
} }
} }
} }

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);

View File

@@ -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'),
); );

View File

@@ -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;
} }

View File

@@ -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,25 +182,22 @@ 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, _s.fileTooLarge(
Text( path,
_s.fileTooLarge( size.convertBytes,
path, privateKeyMaxSize.convertBytes,
size.convertBytes,
privateKeyMaxSize.convertBytes,
),
), ),
); );
return; return;

View File

@@ -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: [

View File

@@ -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: [

View File

@@ -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) {

View File

@@ -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),

View File

@@ -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),
); // );
} // }
} }

View File

@@ -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);

View File

@@ -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;

View File

@@ -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: [

View File

@@ -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,

View File

@@ -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(() {});

View File

@@ -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,23 +152,22 @@ 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: [
ListTile( ListTile(
leading: const Icon(Icons.open_in_new), leading: const Icon(Icons.open_in_new),
title: Text(_s.system), title: Text(_s.system),
onTap: () => context.pop(1), onTap: () => context.pop(1),
), ),
ListTile( ListTile(
leading: const Icon(Icons.folder), leading: const Icon(Icons.folder),
title: Text(_s.inner), title: Text(_s.inner),
onTap: () => context.pop(0), onTap: () => context.pop(0),
), ),
], ],
)); ));
final path = await () async { final path = await () async {
switch (idx) { switch (idx) {
case 0: case 0:
@@ -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, name.filename,
Text(_s.fileTooLarge( size ?? 0,
name.filename, editorMaxSize,
size ?? 0, ));
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: [

View File

@@ -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: [

View File

@@ -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'),

View 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);
},
);
},
);
}
}

View File

@@ -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,

View File

@@ -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;