mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
new & opt.
- new: support suspend and WOL #172 - opt.: `execWithPwd` when cancel - opt.: extentions
This commit is contained in:
@@ -1298,6 +1298,12 @@ abstract class S {
|
|||||||
/// **'Are you sure to delete server [{server}]?'**
|
/// **'Are you sure to delete server [{server}]?'**
|
||||||
String sureToDeleteServer(Object server);
|
String sureToDeleteServer(Object server);
|
||||||
|
|
||||||
|
/// No description provided for @suspendTip.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'The suspend function requires root privileges and systemd support.'**
|
||||||
|
String get suspendTip;
|
||||||
|
|
||||||
/// No description provided for @syncTip.
|
/// No description provided for @syncTip.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -640,6 +640,9 @@ class SDe extends S {
|
|||||||
return 'Bist du sicher, dass du [$server] löschen willst?';
|
return 'Bist du sicher, dass du [$server] löschen willst?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suspendTip => 'Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => 'Nach der automatischen Synchronisierung kann es erforderlich sein, die App neu zu starten, damit bestimmte Änderungen wirksam werden.';
|
String get syncTip => 'Nach der automatischen Synchronisierung kann es erforderlich sein, die App neu zu starten, damit bestimmte Änderungen wirksam werden.';
|
||||||
|
|
||||||
|
|||||||
@@ -640,6 +640,9 @@ class SEn extends S {
|
|||||||
return 'Are you sure to delete server [$server]?';
|
return 'Are you sure to delete server [$server]?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suspendTip => 'The suspend function requires root privileges and systemd support.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => 'After auto sync, a restart may be required for some changes to take effect.';
|
String get syncTip => 'After auto sync, a restart may be required for some changes to take effect.';
|
||||||
|
|
||||||
|
|||||||
@@ -640,6 +640,9 @@ class SId extends S {
|
|||||||
return 'Apakah Anda pasti akan menghapus server [$server]?';
|
return 'Apakah Anda pasti akan menghapus server [$server]?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suspendTip => 'Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => 'Setelah sinkronisasi otomatis, mungkin perlu memulai ulang aplikasi agar perubahan tertentu dapat diterapkan.';
|
String get syncTip => 'Setelah sinkronisasi otomatis, mungkin perlu memulai ulang aplikasi agar perubahan tertentu dapat diterapkan.';
|
||||||
|
|
||||||
|
|||||||
@@ -640,6 +640,9 @@ class SZh extends S {
|
|||||||
return '你确定要删除服务器 [$server] 吗?';
|
return '你确定要删除服务器 [$server] 吗?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suspendTip => 'suspend 功能需要 root 权限及 systemd 支持。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => '在自动同步后,可能需要重新启动,某些更改才能生效。';
|
String get syncTip => '在自动同步后,可能需要重新启动,某些更改才能生效。';
|
||||||
|
|
||||||
@@ -1387,6 +1390,9 @@ class SZhTw extends SZh {
|
|||||||
return '你確定要刪除服務器 [$server] 嗎?';
|
return '你確定要刪除服務器 [$server] 嗎?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suspendTip => 'suspend 功能需要 root 權限及 systemd 支持。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => '在自動同步後,可能需要重新啟動,某些更改才能生效。';
|
String get syncTip => '在自動同步後,可能需要重新啟動,某些更改才能生效。';
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ extension DialogX on BuildContext {
|
|||||||
void showSnippetDialog(
|
void showSnippetDialog(
|
||||||
void Function(Snippet s) onSelected,
|
void Function(Snippet s) onSelected,
|
||||||
) {
|
) {
|
||||||
if (Providers.snippet.snippets.isEmpty) {
|
if (Pros.snippet.snippets.isEmpty) {
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
child: Text(l10n.noSavedSnippet),
|
child: Text(l10n.noSavedSnippet),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -76,12 +76,12 @@ extension DialogX on BuildContext {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var snippet = Providers.snippet.snippets.first;
|
var snippet = Pros.snippet.snippets.first;
|
||||||
showRoundDialog(
|
showRoundDialog(
|
||||||
title: Text(l10n.choose),
|
title: Text(l10n.choose),
|
||||||
child: Picker(
|
child: Picker(
|
||||||
items: Providers.snippet.snippets.map((e) => Text(e.name)).toList(),
|
items: Pros.snippet.snippets.map((e) => Text(e.name)).toList(),
|
||||||
onSelected: (idx) => snippet = Providers.snippet.snippets[idx],
|
onSelected: (idx) => snippet = Pros.snippet.snippets[idx],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|||||||
@@ -74,9 +74,11 @@ extension SSHClientX on SSHClient {
|
|||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
final pwd = await context.showPwdDialog(user);
|
final pwd = await context.showPwdDialog(user);
|
||||||
if (pwd == null || pwd.isEmpty) {
|
if (pwd == null || pwd.isEmpty) {
|
||||||
return;
|
// Add ctrl + c to exit.
|
||||||
|
sink.add('\x03'.uint8List);
|
||||||
|
} else {
|
||||||
|
sink.add('$pwd\n'.uint8List);
|
||||||
}
|
}
|
||||||
sink.add('$pwd\n'.uint8List);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStdout: onStdout,
|
onStdout: onStdout,
|
||||||
|
|||||||
@@ -14,52 +14,5 @@ extension StringX on String {
|
|||||||
return Color(val);
|
return Color(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
int get i => int.parse(this);
|
|
||||||
|
|
||||||
Uri get uri {
|
|
||||||
return Uri.parse(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget omitStartStr({
|
|
||||||
TextStyle? style,
|
|
||||||
TextOverflow? overflow,
|
|
||||||
int? maxLines,
|
|
||||||
}) {
|
|
||||||
return LayoutBuilder(builder: (context, size) {
|
|
||||||
bool exceeded = false;
|
|
||||||
int len = 0;
|
|
||||||
for (; !exceeded && len < length; len++) {
|
|
||||||
// Build the textspan
|
|
||||||
var span = TextSpan(
|
|
||||||
text: 'A' * 7 + substring(length - len),
|
|
||||||
style: style ?? Theme.of(context).textTheme.bodyMedium,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use a textpainter to determine if it will exceed max lines
|
|
||||||
var tp = TextPainter(
|
|
||||||
maxLines: maxLines ?? 1,
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
text: span,
|
|
||||||
);
|
|
||||||
|
|
||||||
// trigger it to layout
|
|
||||||
tp.layout(maxWidth: size.maxWidth);
|
|
||||||
|
|
||||||
// whether the text overflowed or not
|
|
||||||
exceeded = tp.didExceedMaxLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Text(
|
|
||||||
(exceeded ? '...' : '') + substring(length - len),
|
|
||||||
overflow: overflow ?? TextOverflow.fade,
|
|
||||||
softWrap: false,
|
|
||||||
maxLines: maxLines ?? 1,
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String get withLangExport => 'export LANG=en_US.UTF-8 && $this';
|
|
||||||
|
|
||||||
Uint8List get uint8List => Uint8List.fromList(utf8.encode(this));
|
Uint8List get uint8List => Uint8List.fromList(utf8.encode(this));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Providers.app.newestBuild = newest;
|
Pros.app.newestBuild = newest;
|
||||||
|
|
||||||
if (!force && newest <= BuildData.build) {
|
if (!force && newest <= BuildData.build) {
|
||||||
Loggers.app.info('Update ignored: ${BuildData.build} >= $newest');
|
Loggers.app.info('Update ignored: ${BuildData.build} >= $newest');
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ Future<bool> shareFiles(List<String> filePaths) async {
|
|||||||
} else {
|
} else {
|
||||||
text = '${filePaths.length} ${l10n.files}';
|
text = '${filePaths.length} ${l10n.files}';
|
||||||
}
|
}
|
||||||
Providers.app.moveBg = false;
|
Pros.app.moveBg = false;
|
||||||
// ignore: deprecated_member_use
|
// ignore: deprecated_member_use
|
||||||
await Share.shareFiles(filePaths, subject: text);
|
await Share.shareFiles(filePaths, subject: text);
|
||||||
Providers.app.moveBg = true;
|
Pros.app.moveBg = true;
|
||||||
return filePaths.isNotEmpty;
|
return filePaths.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ void copy2Clipboard(String text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> pickOneFile() async {
|
Future<String?> pickOneFile() async {
|
||||||
Providers.app.moveBg = false;
|
Pros.app.moveBg = false;
|
||||||
final result = await FilePicker.platform.pickFiles(type: FileType.any);
|
final result = await FilePicker.platform.pickFiles(type: FileType.any);
|
||||||
Providers.app.moveBg = true;
|
Pros.app.moveBg = true;
|
||||||
return result?.files.single.path;
|
return result?.files.single.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ import 'package:toolbox/core/utils/platform/base.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import 'misc.dart';
|
import 'misc.dart';
|
||||||
import '../extension/stringx.dart';
|
|
||||||
import '../extension/uint8list.dart';
|
import '../extension/uint8list.dart';
|
||||||
|
|
||||||
Future<bool> openUrl(String url) async {
|
Future<bool> openUrl(String url) async {
|
||||||
return await launchUrl(url.uri, mode: LaunchMode.externalApplication);
|
return await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTransparentNavigationBar(BuildContext context) {
|
void setTransparentNavigationBar(BuildContext context) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ enum ShellFunc {
|
|||||||
process,
|
process,
|
||||||
shutdown,
|
shutdown,
|
||||||
reboot,
|
reboot,
|
||||||
|
suspend,
|
||||||
;
|
;
|
||||||
|
|
||||||
String get flag {
|
String get flag {
|
||||||
@@ -32,6 +33,8 @@ enum ShellFunc {
|
|||||||
return 'sd';
|
return 'sd';
|
||||||
case ShellFunc.reboot:
|
case ShellFunc.reboot:
|
||||||
return 'r';
|
return 'r';
|
||||||
|
case ShellFunc.suspend:
|
||||||
|
return 'sp';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +54,12 @@ enum ShellFunc {
|
|||||||
return 'ShutDown';
|
return 'ShutDown';
|
||||||
case ShellFunc.reboot:
|
case ShellFunc.reboot:
|
||||||
return 'Reboot';
|
return 'Reboot';
|
||||||
|
case ShellFunc.suspend:
|
||||||
|
return 'Suspend';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String get cmd {
|
String get _cmd {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case ShellFunc.status:
|
case ShellFunc.status:
|
||||||
return '''
|
return '''
|
||||||
@@ -96,6 +101,13 @@ if [ "\$userId" = "0" ]; then
|
|||||||
\treboot
|
\treboot
|
||||||
else
|
else
|
||||||
\tsudo -S reboot
|
\tsudo -S reboot
|
||||||
|
fi''';
|
||||||
|
case ShellFunc.suspend:
|
||||||
|
return '''
|
||||||
|
if [ "\$userId" = "0" ]; then
|
||||||
|
\tsystemctl suspend
|
||||||
|
else
|
||||||
|
\tsudo -S systemctl suspend
|
||||||
fi''';
|
fi''';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +135,7 @@ userId=\$(id -u)
|
|||||||
for (final func in values) {
|
for (final func in values) {
|
||||||
sb.write('''
|
sb.write('''
|
||||||
${func.name}() {
|
${func.name}() {
|
||||||
${func.cmd.split('\n').map((e) => '\t$e').join('\n')}
|
${func._cmd.split('\n').map((e) => '\t$e').join('\n')}
|
||||||
}
|
}
|
||||||
|
|
||||||
''');
|
''');
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import '../../../core/extension/stringx.dart';
|
|
||||||
import '../../res/misc.dart';
|
import '../../res/misc.dart';
|
||||||
|
|
||||||
class Conn {
|
class Conn {
|
||||||
@@ -22,10 +21,10 @@ Conn? parseConn(String raw) {
|
|||||||
if (idx != '') {
|
if (idx != '') {
|
||||||
final vals = idx.split(Miscs.numReg);
|
final vals = idx.split(Miscs.numReg);
|
||||||
return Conn(
|
return Conn(
|
||||||
maxConn: vals[5].i,
|
maxConn: int.tryParse(vals[5]) ?? 0,
|
||||||
active: vals[6].i,
|
active: int.tryParse(vals[6]) ?? 0,
|
||||||
passive: vals[7].i,
|
passive: int.tryParse(vals[7]) ?? 0,
|
||||||
fail: vals[8].i,
|
fail: int.tryParse(vals[8]) ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ServerPrivateInfo {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Server? get server => Providers.server.pick(spi: this);
|
Server? get server => Pros.server.pick(spi: this);
|
||||||
|
|
||||||
bool shouldReconnect(ServerPrivateInfo old) {
|
bool shouldReconnect(ServerPrivateInfo old) {
|
||||||
return id != old.id ||
|
return id != old.id ||
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'dart:async';
|
|||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toolbox/core/extension/ssh_client.dart';
|
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||||
import 'package:toolbox/core/extension/stringx.dart';
|
|
||||||
import 'package:toolbox/data/model/app/shell_func.dart';
|
import 'package:toolbox/data/model/app/shell_func.dart';
|
||||||
import 'package:toolbox/data/model/docker/image.dart';
|
import 'package:toolbox/data/model/docker/image.dart';
|
||||||
import 'package:toolbox/data/model/docker/ps.dart';
|
import 'package:toolbox/data/model/docker/ps.dart';
|
||||||
@@ -175,9 +174,10 @@ class DockerProvider extends ChangeNotifier {
|
|||||||
// judge whether to use DOCKER_HOST
|
// judge whether to use DOCKER_HOST
|
||||||
String _wrap(String cmd) {
|
String _wrap(String cmd) {
|
||||||
final dockerHost = Stores.docker.fetch(hostId!);
|
final dockerHost = Stores.docker.fetch(hostId!);
|
||||||
|
cmd = 'export LANG=en_US.UTF-8 && $cmd';
|
||||||
if (dockerHost == null || dockerHost.isEmpty) {
|
if (dockerHost == null || dockerHost.isEmpty) {
|
||||||
return cmd.withLangExport;
|
return cmd;
|
||||||
}
|
}
|
||||||
return 'export DOCKER_HOST=$dockerHost && $cmd'.withLangExport;
|
return 'export DOCKER_HOST=$dockerHost && $cmd';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ class ServerProvider extends ChangeNotifier {
|
|||||||
final localPath = joinPath(await Paths.doc, 'install.sh');
|
final localPath = joinPath(await Paths.doc, 'install.sh');
|
||||||
final file = File(localPath);
|
final file = File(localPath);
|
||||||
file.writeAsString(ShellFunc.allScript);
|
file.writeAsString(ShellFunc.allScript);
|
||||||
final sftp = Providers.sftp;
|
final sftp = Pros.sftp;
|
||||||
final completer = Completer();
|
final completer = Completer();
|
||||||
sftp.add(
|
sftp.add(
|
||||||
SftpReq(spi, installShellPath, localPath, SftpReqType.upload),
|
SftpReq(spi, installShellPath, localPath, SftpReqType.upload),
|
||||||
|
|||||||
@@ -31,12 +31,13 @@ class SnippetProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _addInternal() {
|
void _addInternal() {
|
||||||
if (!Stores.setting.fTISBM.fetch() || _snippets.isNotEmpty) {
|
if (!Stores.first.iSSBM.fetch() ||
|
||||||
|
_snippets.any((e) => e.name == installSBM.name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_snippets.add(installSBM);
|
_snippets.add(installSBM);
|
||||||
Stores.snippet.put(installSBM);
|
Stores.snippet.put(installSBM);
|
||||||
Stores.setting.fTISBM.put(false);
|
Stores.first.iSSBM.put(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateTags() {
|
void _updateTags() {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import 'package:toolbox/data/provider/sftp.dart';
|
|||||||
import 'package:toolbox/data/provider/snippet.dart';
|
import 'package:toolbox/data/provider/snippet.dart';
|
||||||
import 'package:toolbox/locator.dart';
|
import 'package:toolbox/locator.dart';
|
||||||
|
|
||||||
class Providers {
|
class Pros {
|
||||||
const Providers._();
|
const Pros._();
|
||||||
|
|
||||||
static final app = locator<AppProvider>();
|
static final app = locator<AppProvider>();
|
||||||
static final debug = locator<DebugProvider>();
|
static final debug = locator<DebugProvider>();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:toolbox/data/store/docker.dart';
|
import 'package:toolbox/data/store/docker.dart';
|
||||||
|
import 'package:toolbox/data/store/first.dart';
|
||||||
import 'package:toolbox/data/store/history.dart';
|
import 'package:toolbox/data/store/history.dart';
|
||||||
import 'package:toolbox/data/store/private_key.dart';
|
import 'package:toolbox/data/store/private_key.dart';
|
||||||
import 'package:toolbox/data/store/server.dart';
|
import 'package:toolbox/data/store/server.dart';
|
||||||
@@ -15,4 +16,5 @@ class Stores {
|
|||||||
static final history = locator<HistoryStore>();
|
static final history = locator<HistoryStore>();
|
||||||
static final key = locator<PrivateKeyStore>();
|
static final key = locator<PrivateKeyStore>();
|
||||||
static final snippet = locator<SnippetStore>();
|
static final snippet = locator<SnippetStore>();
|
||||||
|
static final first = locator<FirstStore>();
|
||||||
}
|
}
|
||||||
|
|||||||
10
lib/data/store/first.dart
Normal file
10
lib/data/store/first.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:toolbox/core/persistant_store.dart';
|
||||||
|
|
||||||
|
/// It stores whether is the first time of some.
|
||||||
|
class FirstStore extends PersistentStore<bool> {
|
||||||
|
/// Add Snippet `Install ServerBoxMonitor`
|
||||||
|
late final iSSBM = StoreProperty(box, 'installMonitorSnippet', true);
|
||||||
|
|
||||||
|
/// Show tip of suspend
|
||||||
|
late final showSuspendTip = StoreProperty(box, 'showSuspendTip', true);
|
||||||
|
}
|
||||||
@@ -205,7 +205,7 @@ class SettingStore extends PersistentStore {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Only valid on iOS / Android
|
/// Only valid on iOS / Android / Windows
|
||||||
late final useBioAuth = StoreProperty(
|
late final useBioAuth = StoreProperty(
|
||||||
box,
|
box,
|
||||||
'useBioAuth',
|
'useBioAuth',
|
||||||
@@ -213,15 +213,11 @@ class SettingStore extends PersistentStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Never show these settings for users
|
// Never show these settings for users
|
||||||
// Guide for these settings:
|
|
||||||
// - key should start with `_` and be shorter as possible
|
|
||||||
//
|
//
|
||||||
// ------BEGIN------
|
// ------BEGIN------
|
||||||
|
|
||||||
/// Version of store db
|
/// Version of store db
|
||||||
late final storeVersion = StoreProperty(box, 'storeVersion', 0);
|
late final storeVersion = StoreProperty(box, 'storeVersion', 0);
|
||||||
|
|
||||||
/// Whether is first time to add Snippet<Install ServerBoxMonitor>
|
|
||||||
late final fTISBM = StoreProperty(box, '_fTISBM', true);
|
|
||||||
// ------END------
|
// ------END------
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,6 +200,7 @@
|
|||||||
"sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?",
|
"sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?",
|
||||||
"sureStop": "Sind Sie sicher, dass Sie [{item}] stoppen möchten?",
|
"sureStop": "Sind Sie sicher, dass Sie [{item}] stoppen möchten?",
|
||||||
"sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?",
|
"sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?",
|
||||||
|
"suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.",
|
||||||
"syncTip": "Nach der automatischen Synchronisierung kann es erforderlich sein, die App neu zu starten, damit bestimmte Änderungen wirksam werden.",
|
"syncTip": "Nach der automatischen Synchronisierung kann es erforderlich sein, die App neu zu starten, damit bestimmte Änderungen wirksam werden.",
|
||||||
"system": "Systeme",
|
"system": "Systeme",
|
||||||
"tag": "Tags",
|
"tag": "Tags",
|
||||||
|
|||||||
@@ -200,6 +200,7 @@
|
|||||||
"sureNoPwd": "Are you sure to use no password?",
|
"sureNoPwd": "Are you sure to use no password?",
|
||||||
"sureStop": "Sure to stop [{item}] ?",
|
"sureStop": "Sure to stop [{item}] ?",
|
||||||
"sureToDeleteServer": "Are you sure to delete server [{server}]?",
|
"sureToDeleteServer": "Are you sure to delete server [{server}]?",
|
||||||
|
"suspendTip": "The suspend function requires root privileges and systemd support.",
|
||||||
"syncTip": "After auto sync, a restart may be required for some changes to take effect.",
|
"syncTip": "After auto sync, a restart may be required for some changes to take effect.",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"tag": "Tags",
|
"tag": "Tags",
|
||||||
|
|||||||
@@ -200,6 +200,7 @@
|
|||||||
"sureNoPwd": "Apakah Anda pasti tidak menggunakan kata sandi?",
|
"sureNoPwd": "Apakah Anda pasti tidak menggunakan kata sandi?",
|
||||||
"sureStop": "Anda yakin ingin menghentikan [{item}]?",
|
"sureStop": "Anda yakin ingin menghentikan [{item}]?",
|
||||||
"sureToDeleteServer": "Apakah Anda pasti akan menghapus server [{server}]?",
|
"sureToDeleteServer": "Apakah Anda pasti akan menghapus server [{server}]?",
|
||||||
|
"suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.",
|
||||||
"syncTip": "Setelah sinkronisasi otomatis, mungkin perlu memulai ulang aplikasi agar perubahan tertentu dapat diterapkan.",
|
"syncTip": "Setelah sinkronisasi otomatis, mungkin perlu memulai ulang aplikasi agar perubahan tertentu dapat diterapkan.",
|
||||||
"system": "Sistem",
|
"system": "Sistem",
|
||||||
"tag": "Tag",
|
"tag": "Tag",
|
||||||
|
|||||||
@@ -200,6 +200,7 @@
|
|||||||
"sureNoPwd": "确认使用无密码?",
|
"sureNoPwd": "确认使用无密码?",
|
||||||
"sureStop": "确定要停止 [{item}] 吗?",
|
"sureStop": "确定要停止 [{item}] 吗?",
|
||||||
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
|
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
|
||||||
|
"suspendTip": "suspend 功能需要 root 权限及 systemd 支持。",
|
||||||
"syncTip": "在自动同步后,可能需要重新启动,某些更改才能生效。",
|
"syncTip": "在自动同步后,可能需要重新启动,某些更改才能生效。",
|
||||||
"system": "系统",
|
"system": "系统",
|
||||||
"tag": "标签",
|
"tag": "标签",
|
||||||
|
|||||||
@@ -200,6 +200,7 @@
|
|||||||
"sureNoPwd": "確認使用無密碼?",
|
"sureNoPwd": "確認使用無密碼?",
|
||||||
"sureStop": "確定要停止 [{item}] 嗎?",
|
"sureStop": "確定要停止 [{item}] 嗎?",
|
||||||
"sureToDeleteServer": "你確定要刪除服務器 [{server}] 嗎?",
|
"sureToDeleteServer": "你確定要刪除服務器 [{server}] 嗎?",
|
||||||
|
"suspendTip": "suspend 功能需要 root 權限及 systemd 支持。",
|
||||||
"syncTip": "在自動同步後,可能需要重新啟動,某些更改才能生效。",
|
"syncTip": "在自動同步後,可能需要重新啟動,某些更改才能生效。",
|
||||||
"system": "系統",
|
"system": "系統",
|
||||||
"tag": "标签",
|
"tag": "标签",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:toolbox/data/store/first.dart';
|
||||||
|
|
||||||
import 'data/provider/app.dart';
|
import 'data/provider/app.dart';
|
||||||
import 'data/provider/debug.dart';
|
import 'data/provider/debug.dart';
|
||||||
@@ -57,6 +58,10 @@ Future<void> _setupLocatorForStores() async {
|
|||||||
final history = HistoryStore();
|
final history = HistoryStore();
|
||||||
await history.init(boxName: 'history');
|
await history.init(boxName: 'history');
|
||||||
locator.registerSingleton(history);
|
locator.registerSingleton(history);
|
||||||
|
|
||||||
|
final first = FirstStore();
|
||||||
|
await first.init(boxName: 'first');
|
||||||
|
locator.registerSingleton(first);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setupLocator() async {
|
Future<void> setupLocator() async {
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ Future<void> initApp() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _setupProviders() {
|
void _setupProviders() {
|
||||||
Providers.snippet.load();
|
Pros.snippet.load();
|
||||||
Providers.key.load();
|
Pros.key.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initHive() async {
|
Future<void> _initHive() async {
|
||||||
@@ -109,7 +109,7 @@ Future<void> _initHive() async {
|
|||||||
void _setupLogger() {
|
void _setupLogger() {
|
||||||
Logger.root.level = Level.ALL;
|
Logger.root.level = Level.ALL;
|
||||||
Logger.root.onRecord.listen((record) {
|
Logger.root.onRecord.listen((record) {
|
||||||
Providers.debug.addLog(record);
|
Pros.debug.addLog(record);
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print(record);
|
print(record);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class BackupPage extends StatelessWidget {
|
|||||||
backup.restore();
|
backup.restore();
|
||||||
context.pop();
|
context.pop();
|
||||||
RebuildNodes.app.rebuild();
|
RebuildNodes.app.rebuild();
|
||||||
Providers.reload();
|
Pros.reload();
|
||||||
},
|
},
|
||||||
child: Text(l10n.ok),
|
child: Text(l10n.ok),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
Providers.docker.clear();
|
Pros.docker.clear();
|
||||||
_textController.dispose();
|
_textController.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
if (client == null) {
|
if (client == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Providers.docker
|
Pros.docker
|
||||||
..init(
|
..init(
|
||||||
client,
|
client,
|
||||||
widget.spi.user,
|
widget.spi.user,
|
||||||
@@ -70,7 +70,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.showLoadingDialog();
|
context.showLoadingDialog();
|
||||||
await Providers.docker.refresh();
|
await Pros.docker.refresh();
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
@@ -78,8 +78,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: _buildMain(),
|
body: _buildMain(),
|
||||||
floatingActionButton:
|
floatingActionButton: Pros.docker.error == null ? _buildFAB() : null,
|
||||||
Providers.docker.error == null ? _buildFAB() : null,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -156,7 +155,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
context.showLoadingDialog();
|
context.showLoadingDialog();
|
||||||
final result = await Providers.docker.run(cmd);
|
final result = await Pros.docker.run(cmd);
|
||||||
context.pop();
|
context.pop();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
context.showSnackBar(result.message ?? l10n.unknownError);
|
context.showSnackBar(result.message ?? l10n.unknownError);
|
||||||
@@ -182,7 +181,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMain() {
|
Widget _buildMain() {
|
||||||
if (Providers.docker.error != null && Providers.docker.items == null) {
|
if (Pros.docker.error != null && Pros.docker.items == null) {
|
||||||
return SizedBox.expand(
|
return SizedBox.expand(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -193,17 +192,17 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
size: 37,
|
size: 37,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 27),
|
const SizedBox(height: 27),
|
||||||
Text(Providers.docker.error?.message ?? l10n.unknownError),
|
Text(Pros.docker.error?.message ?? l10n.unknownError),
|
||||||
const SizedBox(height: 27),
|
const SizedBox(height: 27),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(17),
|
padding: const EdgeInsets.all(17),
|
||||||
child: _buildSolution(Providers.docker.error!),
|
child: _buildSolution(Pros.docker.error!),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (Providers.docker.items == null || Providers.docker.images == null) {
|
if (Pros.docker.items == null || Pros.docker.images == null) {
|
||||||
return UIs.centerLoading;
|
return UIs.centerLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,12 +224,12 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(l10n.imagesList),
|
title: Text(l10n.imagesList),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
l10n.dockerImagesFmt(Providers.docker.images!.length),
|
l10n.dockerImagesFmt(Pros.docker.images!.length),
|
||||||
style: UIs.textGrey,
|
style: UIs.textGrey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
items.addAll(Providers.docker.images!.map(_buildImageItem));
|
items.addAll(Pros.docker.images!.map(_buildImageItem));
|
||||||
return Column(children: items);
|
return Column(children: items);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +258,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
final result = await Providers.docker.run(
|
final result = await Pros.docker.run(
|
||||||
'docker rmi ${e.id} -f',
|
'docker rmi ${e.id} -f',
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@@ -273,7 +272,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoading() {
|
Widget _buildLoading() {
|
||||||
if (Providers.docker.runLog == null) return UIs.placeholder;
|
if (Pros.docker.runLog == null) return UIs.placeholder;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(17),
|
padding: const EdgeInsets.all(17),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -282,7 +281,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
UIs.height13,
|
UIs.height13,
|
||||||
Text(Providers.docker.runLog ?? '...'),
|
Text(Pros.docker.runLog ?? '...'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -323,8 +322,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(Providers.docker.edition ?? l10n.unknown),
|
Text(Pros.docker.edition ?? l10n.unknown),
|
||||||
Text(Providers.docker.version ?? l10n.unknown),
|
Text(Pros.docker.version ?? l10n.unknown),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -335,12 +334,12 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(l10n.containerStatus),
|
title: Text(l10n.containerStatus),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
_buildPsCardSubtitle(Providers.docker.items!),
|
_buildPsCardSubtitle(Pros.docker.items!),
|
||||||
style: UIs.textGrey,
|
style: UIs.textGrey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
items.addAll(Providers.docker.items!.map(_buildPsItem));
|
items.addAll(Pros.docker.items!.map(_buildPsItem));
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: items,
|
children: items,
|
||||||
@@ -372,7 +371,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
context.showLoadingDialog();
|
context.showLoadingDialog();
|
||||||
await Providers.docker.delete(dItem.containerId);
|
await Pros.docker.delete(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
child: Text(l10n.ok),
|
child: Text(l10n.ok),
|
||||||
@@ -382,17 +381,17 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
break;
|
break;
|
||||||
case DockerMenuType.start:
|
case DockerMenuType.start:
|
||||||
context.showLoadingDialog();
|
context.showLoadingDialog();
|
||||||
await Providers.docker.start(dItem.containerId);
|
await Pros.docker.start(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
break;
|
break;
|
||||||
case DockerMenuType.stop:
|
case DockerMenuType.stop:
|
||||||
context.showLoadingDialog();
|
context.showLoadingDialog();
|
||||||
await Providers.docker.stop(dItem.containerId);
|
await Pros.docker.stop(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
break;
|
break;
|
||||||
case DockerMenuType.restart:
|
case DockerMenuType.restart:
|
||||||
context.showLoadingDialog();
|
context.showLoadingDialog();
|
||||||
await Providers.docker.restart(dItem.containerId);
|
await Pros.docker.restart(dItem.containerId);
|
||||||
context.pop();
|
context.pop();
|
||||||
break;
|
break;
|
||||||
case DockerMenuType.logs:
|
case DockerMenuType.logs:
|
||||||
@@ -441,7 +440,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
|
|
||||||
Widget _buildEditHost() {
|
Widget _buildEditHost() {
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
if (Providers.docker.items!.isEmpty && Providers.docker.images!.isEmpty) {
|
if (Pros.docker.items!.isEmpty && Pros.docker.images!.isEmpty) {
|
||||||
children.add(Padding(
|
children.add(Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),
|
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -484,6 +483,6 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
void _onSaveDockerHost(String val) {
|
void _onSaveDockerHost(String val) {
|
||||||
context.pop();
|
context.pop();
|
||||||
Stores.docker.put(widget.spi.id, val.trim());
|
Stores.docker.put(widget.spi.id, val.trim());
|
||||||
Providers.docker.refresh();
|
Pros.docker.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,8 +368,8 @@ class _FullScreenPageState extends State<FullScreenPage> with AfterLayoutMixin {
|
|||||||
doUpdate(context);
|
doUpdate(context);
|
||||||
}
|
}
|
||||||
await GetIt.I.allReady();
|
await GetIt.I.allReady();
|
||||||
await Providers.server.load();
|
await Pros.server.load();
|
||||||
await Providers.server.refreshData();
|
await Pros.server.refreshData();
|
||||||
if (!Analysis.enabled) {
|
if (!Analysis.enabled) {
|
||||||
await Analysis.init();
|
await Analysis.init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
Providers.server.closeServer();
|
Pros.server.closeServer();
|
||||||
_pageController.dispose();
|
_pageController.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,20 +84,20 @@ class _HomePageState extends State<HomePage>
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case AppLifecycleState.resumed:
|
case AppLifecycleState.resumed:
|
||||||
_auth();
|
_auth();
|
||||||
if (!Providers.server.isAutoRefreshOn) {
|
if (!Pros.server.isAutoRefreshOn) {
|
||||||
Providers.server.startAutoRefresh();
|
Pros.server.startAutoRefresh();
|
||||||
}
|
}
|
||||||
updateHomeWidget();
|
updateHomeWidget();
|
||||||
break;
|
break;
|
||||||
case AppLifecycleState.paused:
|
case AppLifecycleState.paused:
|
||||||
// Keep running in background on Android device
|
// Keep running in background on Android device
|
||||||
if (isAndroid && Stores.setting.bgRun.fetch()) {
|
if (isAndroid && Stores.setting.bgRun.fetch()) {
|
||||||
if (Providers.app.moveBg) {
|
if (Pros.app.moveBg) {
|
||||||
BgRunMC.moveToBg();
|
BgRunMC.moveToBg();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Providers.server.setDisconnected();
|
Pros.server.setDisconnected();
|
||||||
Providers.server.stopAutoRefresh();
|
Pros.server.stopAutoRefresh();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -148,7 +148,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
return IconButton(
|
return IconButton(
|
||||||
icon: const Icon(Icons.refresh, size: 23),
|
icon: const Icon(Icons.refresh, size: 23),
|
||||||
tooltip: 'Refresh',
|
tooltip: 'Refresh',
|
||||||
onPressed: () => Providers.server.refreshData(onlyFailed: true),
|
onPressed: () => Pros.server.refreshData(onlyFailed: true),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -343,8 +343,8 @@ class _HomePageState extends State<HomePage>
|
|||||||
}
|
}
|
||||||
updateHomeWidget();
|
updateHomeWidget();
|
||||||
await GetIt.I.allReady();
|
await GetIt.I.allReady();
|
||||||
await Providers.server.load();
|
await Pros.server.load();
|
||||||
await Providers.server.refreshData();
|
await Pros.server.refreshData();
|
||||||
if (!Analysis.enabled) {
|
if (!Analysis.enabled) {
|
||||||
Analysis.init();
|
Analysis.init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class _PingPageState extends State<PingPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Providers.server.serverOrder.isEmpty) {
|
if (Pros.server.serverOrder.isEmpty) {
|
||||||
context.showSnackBar(l10n.pingNoServer);
|
context.showSnackBar(l10n.pingNoServer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,7 @@ class _PingPageState extends State<PingPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Future.wait(Providers.server.servers.map((e) async {
|
await Future.wait(Pros.server.servers.map((e) async {
|
||||||
if (e.client == null) {
|
if (e.client == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Providers.key.delete(widget.pki!);
|
Pros.key.delete(widget.pki!);
|
||||||
context.pop();
|
context.pop();
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
@@ -135,9 +135,9 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
|||||||
final decrypted = await compute(decyptPem, [key, pwd]);
|
final decrypted = await compute(decyptPem, [key, pwd]);
|
||||||
final pki = PrivateKeyInfo(id: name, key: decrypted);
|
final pki = PrivateKeyInfo(id: name, key: decrypted);
|
||||||
if (widget.pki != null) {
|
if (widget.pki != null) {
|
||||||
Providers.key.update(widget.pki!, pki);
|
Pros.key.update(widget.pki!, pki);
|
||||||
} else {
|
} else {
|
||||||
Providers.key.add(pki);
|
Pros.key.add(pki);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.showSnackBar(e.toString());
|
context.showSnackBar(e.toString());
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
if (widget.spi?.pubKeyId == null) {
|
if (widget.spi?.pubKeyId == null) {
|
||||||
_passwordController.text = widget.spi?.pwd ?? '';
|
_passwordController.text = widget.spi?.pwd ?? '';
|
||||||
} else {
|
} else {
|
||||||
_keyIdx.value = Providers.key.pkis.indexWhere(
|
_keyIdx.value = Pros.key.pkis.indexWhere(
|
||||||
(e) => e.id == widget.spi!.pubKeyId,
|
(e) => e.id == widget.spi!.pubKeyId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Providers.server.delServer(widget.spi!.id);
|
Pros.server.delServer(widget.spi!.id);
|
||||||
context.pop();
|
context.pop();
|
||||||
context.pop(true);
|
context.pop(true);
|
||||||
},
|
},
|
||||||
@@ -183,8 +183,8 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
TagEditor(
|
TagEditor(
|
||||||
tags: _tags,
|
tags: _tags,
|
||||||
onChanged: (p0) => _tags = p0,
|
onChanged: (p0) => _tags = p0,
|
||||||
allTags: [...Providers.server.tags],
|
allTags: [...Pros.server.tags],
|
||||||
onRenameTag: Providers.server.renameTag,
|
onRenameTag: Pros.server.renameTag,
|
||||||
),
|
),
|
||||||
_buildAuth(),
|
_buildAuth(),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -357,7 +357,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
user: _usernameController.text,
|
user: _usernameController.text,
|
||||||
pwd: _passwordController.text.isEmpty ? null : _passwordController.text,
|
pwd: _passwordController.text.isEmpty ? null : _passwordController.text,
|
||||||
pubKeyId: _keyIdx.value != null
|
pubKeyId: _keyIdx.value != null
|
||||||
? Providers.key.pkis.elementAt(_keyIdx.value!).id
|
? Pros.key.pkis.elementAt(_keyIdx.value!).id
|
||||||
: null,
|
: null,
|
||||||
tags: _tags,
|
tags: _tags,
|
||||||
alterUrl: _altUrlController.text.isEmpty ? null : _altUrlController.text,
|
alterUrl: _altUrlController.text.isEmpty ? null : _altUrlController.text,
|
||||||
@@ -365,9 +365,9 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (widget.spi == null) {
|
if (widget.spi == null) {
|
||||||
Providers.server.addServer(spi);
|
Pros.server.addServer(spi);
|
||||||
} else {
|
} else {
|
||||||
Providers.server.updateServer(widget.spi!, spi);
|
Pros.server.updateServer(widget.spi!, spi);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.pop();
|
context.pop();
|
||||||
|
|||||||
@@ -92,8 +92,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async =>
|
onRefresh: () async => await Pros.server.refreshData(onlyFailed: true),
|
||||||
await Providers.server.refreshData(onlyFailed: true),
|
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -245,22 +244,42 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => srv.client?.execWithPwd(
|
onPressed: () async {
|
||||||
ShellFunc.shutdown.cmd,
|
if (Stores.first.showSuspendTip.fetch()) {
|
||||||
context: context,
|
await context.showRoundDialog(
|
||||||
),
|
title: Text(l10n.attention),
|
||||||
icon: const Icon(Icons.power_off),
|
child: Text(l10n.suspendTip),
|
||||||
|
);
|
||||||
|
Stores.first.showSuspendTip.put(false);
|
||||||
|
}
|
||||||
|
srv.client?.execWithPwd(
|
||||||
|
ShellFunc.suspend.exec,
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.stop),
|
||||||
|
tooltip: 'Suspend',
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => srv.client?.execWithPwd(
|
onPressed: () => srv.client?.execWithPwd(
|
||||||
ShellFunc.reboot.cmd,
|
ShellFunc.shutdown.exec,
|
||||||
context: context,
|
context: context,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.power_off),
|
||||||
|
tooltip: 'Shutdown',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => srv.client?.execWithPwd(
|
||||||
|
ShellFunc.reboot.exec,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.restart_alt),
|
||||||
|
tooltip: 'Reboot',
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => AppRoute.serverEdit(spi: srv.spi).go(context),
|
onPressed: () => AppRoute.serverEdit(spi: srv.spi).go(context),
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: l10n.edit,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -305,7 +324,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
Widget? rightCorner;
|
Widget? rightCorner;
|
||||||
if (!(spi.autoConnect ?? true) && cs == ServerState.disconnected) {
|
if (!(spi.autoConnect ?? true) && cs == ServerState.disconnected) {
|
||||||
rightCorner = InkWell(
|
rightCorner = InkWell(
|
||||||
onTap: () => Providers.server.refreshData(spi: spi),
|
onTap: () => Pros.server.refreshData(spi: spi),
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 7),
|
padding: EdgeInsets.symmetric(horizontal: 7),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
@@ -456,8 +475,8 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
@override
|
@override
|
||||||
Future<void> afterFirstLayout(BuildContext context) async {
|
Future<void> afterFirstLayout(BuildContext context) async {
|
||||||
await GetIt.I.allReady();
|
await GetIt.I.allReady();
|
||||||
await Providers.server.load();
|
await Pros.server.load();
|
||||||
Providers.server.startAutoRefresh();
|
Pros.server.startAutoRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _filterServers(ServerProvider pro) => pro.serverOrder
|
List<String> _filterServers(ServerProvider pro) => pro.serverOrder
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
onSelected: (int val) {
|
onSelected: (int val) {
|
||||||
_updateInterval.value = val;
|
_updateInterval.value = val;
|
||||||
_setting.serverStatusUpdateInterval.put(val);
|
_setting.serverStatusUpdateInterval.put(val);
|
||||||
Providers.server.startAutoRefresh();
|
Pros.server.startAutoRefresh();
|
||||||
if (val == 0) {
|
if (val == 0) {
|
||||||
context.showSnackBar(l10n.updateIntervalEqual0);
|
context.showSnackBar(l10n.updateIntervalEqual0);
|
||||||
}
|
}
|
||||||
@@ -883,7 +883,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
child: Text(l10n.sureDelete(e)),
|
child: Text(l10n.sureDelete(e)),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Providers.server.delServer(e),
|
onPressed: () => Pros.server.delServer(e),
|
||||||
child: Text(l10n.ok),
|
child: Text(l10n.ok),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ class _ServerOrderPageState extends State<ServerOrderPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody() {
|
Widget _buildBody() {
|
||||||
if (Providers.server.serverOrder.isEmpty) {
|
if (Pros.server.serverOrder.isEmpty) {
|
||||||
return Center(child: Text(l10n.noServerAvailable));
|
return Center(child: Text(l10n.noServerAvailable));
|
||||||
}
|
}
|
||||||
return ReorderableListView.builder(
|
return ReorderableListView.builder(
|
||||||
footer: const SizedBox(height: 77),
|
footer: const SizedBox(height: 77),
|
||||||
onReorder: (oldIndex, newIndex) => setState(() {
|
onReorder: (oldIndex, newIndex) => setState(() {
|
||||||
Providers.server.serverOrder.move(
|
Pros.server.serverOrder.move(
|
||||||
oldIndex,
|
oldIndex,
|
||||||
newIndex,
|
newIndex,
|
||||||
property: Stores.setting.serverOrder,
|
property: Stores.setting.serverOrder,
|
||||||
@@ -41,13 +41,13 @@ class _ServerOrderPageState extends State<ServerOrderPage> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
|
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
|
||||||
buildDefaultDragHandles: false,
|
buildDefaultDragHandles: false,
|
||||||
itemBuilder: (_, index) =>
|
itemBuilder: (_, index) =>
|
||||||
_buildItem(index, Providers.server.serverOrder[index]),
|
_buildItem(index, Pros.server.serverOrder[index]),
|
||||||
itemCount: Providers.server.serverOrder.length,
|
itemCount: Pros.server.serverOrder.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildItem(int index, String id) {
|
Widget _buildItem(int index, String id) {
|
||||||
final spi = Providers.server.pick(id: id)?.spi;
|
final spi = Pros.server.pick(id: id)?.spi;
|
||||||
if (spi == null) {
|
if (spi == null) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
return [
|
return [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Providers.snippet.del(widget.snippet!);
|
Pros.snippet.del(widget.snippet!);
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
tooltip: l10n.delete,
|
tooltip: l10n.delete,
|
||||||
@@ -84,9 +84,9 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
note: note.isEmpty ? null : note,
|
note: note.isEmpty ? null : note,
|
||||||
);
|
);
|
||||||
if (widget.snippet != null) {
|
if (widget.snippet != null) {
|
||||||
Providers.snippet.update(widget.snippet!, snippet);
|
Pros.snippet.update(widget.snippet!, snippet);
|
||||||
} else {
|
} else {
|
||||||
Providers.snippet.add(snippet);
|
Pros.snippet.add(snippet);
|
||||||
}
|
}
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
@@ -118,9 +118,9 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
onChanged: (p0) => setState(() {
|
onChanged: (p0) => setState(() {
|
||||||
_tags = p0;
|
_tags = p0;
|
||||||
}),
|
}),
|
||||||
allTags: [...Providers.server.tags],
|
allTags: [...Pros.server.tags],
|
||||||
onRenameTag: (old, n) => setState(() {
|
onRenameTag: (old, n) => setState(() {
|
||||||
Providers.server.renameTag(old, n);
|
Pros.server.renameTag(old, n);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Input(
|
Input(
|
||||||
|
|||||||
@@ -130,15 +130,15 @@ class _SnippetListPageState extends State<SnippetListPage> {
|
|||||||
final servers = await showDialog<List<Server>>(
|
final servers = await showDialog<List<Server>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => TagPicker<Server>(
|
builder: (_) => TagPicker<Server>(
|
||||||
items: Providers.server.servers.toList(),
|
items: Pros.server.servers.toList(),
|
||||||
tags: Providers.server.tags.toSet(),
|
tags: Pros.server.tags.toSet(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (servers == null) {
|
if (servers == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final ids = servers.map((e) => e.spi.id).toList();
|
final ids = servers.map((e) => e.spi.id).toList();
|
||||||
final results = await Providers.server.runSnippetsMulti(ids, [snippet]);
|
final results = await Pros.server.runSnippetsMulti(ids, [snippet]);
|
||||||
if (results.isNotEmpty) {
|
if (results.isNotEmpty) {
|
||||||
// SERVER_NAME: RESULT
|
// SERVER_NAME: RESULT
|
||||||
final result = Map.fromIterables(
|
final result = Map.fromIterables(
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import 'package:toolbox/data/model/sftp/req.dart';
|
|||||||
import 'package:toolbox/data/res/misc.dart';
|
import 'package:toolbox/data/res/misc.dart';
|
||||||
import 'package:toolbox/data/res/provider.dart';
|
import 'package:toolbox/data/res/provider.dart';
|
||||||
import 'package:toolbox/view/widget/input_field.dart';
|
import 'package:toolbox/view/widget/input_field.dart';
|
||||||
|
import 'package:toolbox/view/widget/omit_start_text.dart';
|
||||||
import 'package:toolbox/view/widget/picker.dart';
|
import 'package:toolbox/view/widget/picker.dart';
|
||||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
|
|
||||||
import '../../../core/extension/numx.dart';
|
import '../../../core/extension/numx.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 '../../../data/model/app/path_with_prefix.dart';
|
import '../../../data/model/app/path_with_prefix.dart';
|
||||||
@@ -89,7 +89,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
(_path?.path ?? l10n.loadingFiles).omitStartStr(),
|
OmitStartText(_path?.path ?? l10n.loadingFiles),
|
||||||
_buildBtns(),
|
_buildBtns(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -275,7 +275,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
title: Text(l10n.upload),
|
title: Text(l10n.upload),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
final ids = Providers.server.serverOrder;
|
final ids = Pros.server.serverOrder;
|
||||||
var idx = 0;
|
var idx = 0;
|
||||||
await context.showRoundDialog(
|
await context.showRoundDialog(
|
||||||
title: Text(l10n.server),
|
title: Text(l10n.server),
|
||||||
@@ -289,7 +289,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
final id = ids[idx];
|
final id = ids[idx];
|
||||||
final spi = Providers.server.pick(id: id)?.spi;
|
final spi = Pros.server.pick(id: id)?.spi;
|
||||||
if (spi == null) {
|
if (spi == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -300,7 +300,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
|||||||
if (remotePath == null) {
|
if (remotePath == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Providers.sftp.add(SftpReq(
|
Pros.sftp.add(SftpReq(
|
||||||
spi,
|
spi,
|
||||||
'$remotePath/$fileName',
|
'$remotePath/$fileName',
|
||||||
file.absolute.path,
|
file.absolute.path,
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import 'package:toolbox/data/res/logger.dart';
|
|||||||
import 'package:toolbox/data/res/misc.dart';
|
import 'package:toolbox/data/res/misc.dart';
|
||||||
import 'package:toolbox/data/res/provider.dart';
|
import 'package:toolbox/data/res/provider.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:toolbox/data/res/store.dart';
|
||||||
|
import 'package:toolbox/view/widget/omit_start_text.dart';
|
||||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||||
|
|
||||||
import '../../../core/extension/numx.dart';
|
import '../../../core/extension/numx.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 '../../../data/model/server/server_private_info.dart';
|
import '../../../data/model/server/server_private_info.dart';
|
||||||
@@ -113,7 +113,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
(_status.path?.path ?? l10n.loadingFiles).omitStartStr(),
|
OmitStartText(_status.path?.path ?? l10n.loadingFiles),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: children,
|
children: children,
|
||||||
@@ -162,7 +162,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
context.showSnackBar('remote path is null');
|
context.showSnackBar('remote path is null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Providers.sftp.add(
|
Pros.sftp.add(
|
||||||
SftpReq(
|
SftpReq(
|
||||||
widget.spi,
|
widget.spi,
|
||||||
'$remotePath/${path.split('/').last}',
|
'$remotePath/${path.split('/').last}',
|
||||||
@@ -362,14 +362,14 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
localPath,
|
localPath,
|
||||||
SftpReqType.download,
|
SftpReqType.download,
|
||||||
);
|
);
|
||||||
Providers.sftp.add(req, completer: completer);
|
Pros.sftp.add(req, completer: completer);
|
||||||
context.showLoadingDialog();
|
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) {
|
||||||
Providers.sftp
|
Pros.sftp
|
||||||
.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
|
.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
|
||||||
context.showSnackBar(l10n.added2List);
|
context.showSnackBar(l10n.added2List);
|
||||||
}
|
}
|
||||||
@@ -389,7 +389,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
context.pop();
|
context.pop();
|
||||||
final remotePath = _getRemotePath(name);
|
final remotePath = _getRemotePath(name);
|
||||||
|
|
||||||
Providers.sftp.add(
|
Pros.sftp.add(
|
||||||
SftpReq(
|
SftpReq(
|
||||||
widget.spi,
|
widget.spi,
|
||||||
remotePath,
|
remotePath,
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class _SftpMissionPageState extends State<SftpMissionPage> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Providers.sftp.cancel(id);
|
Pros.sftp.cancel(id);
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
child: Text(l10n.ok),
|
child: Text(l10n.ok),
|
||||||
|
|||||||
52
lib/view/widget/omit_start_text.dart
Normal file
52
lib/view/widget/omit_start_text.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class OmitStartText extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
final int? maxLines;
|
||||||
|
final TextStyle? style;
|
||||||
|
final TextOverflow? overflow;
|
||||||
|
|
||||||
|
const OmitStartText(
|
||||||
|
this.text, {
|
||||||
|
Key? key,
|
||||||
|
this.maxLines,
|
||||||
|
this.style,
|
||||||
|
this.overflow,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(builder: (context, size) {
|
||||||
|
bool exceeded = false;
|
||||||
|
int len = 0;
|
||||||
|
for (; !exceeded && len < text.length; len++) {
|
||||||
|
// Build the textspan
|
||||||
|
var span = TextSpan(
|
||||||
|
text: 'A' * 7 + text.substring(text.length - len),
|
||||||
|
style: style ?? Theme.of(context).textTheme.bodyMedium,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use a textpainter to determine if it will exceed max lines
|
||||||
|
var tp = TextPainter(
|
||||||
|
maxLines: maxLines ?? 1,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
text: span,
|
||||||
|
);
|
||||||
|
|
||||||
|
// trigger it to layout
|
||||||
|
tp.layout(maxWidth: size.maxWidth);
|
||||||
|
|
||||||
|
// whether the text overflowed or not
|
||||||
|
exceeded = tp.didExceedMaxLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text(
|
||||||
|
(exceeded ? '...' : '') + text.substring(text.length - len),
|
||||||
|
overflow: overflow ?? TextOverflow.fade,
|
||||||
|
softWrap: false,
|
||||||
|
maxLines: maxLines ?? 1,
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,14 +99,14 @@ void _onTapMoreBtns(
|
|||||||
final snippets = await showDialog<List<Snippet>>(
|
final snippets = await showDialog<List<Snippet>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => TagPicker<Snippet>(
|
builder: (_) => TagPicker<Snippet>(
|
||||||
items: Providers.snippet.snippets,
|
items: Pros.snippet.snippets,
|
||||||
tags: Providers.server.tags.toSet(),
|
tags: Pros.server.tags.toSet(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (snippets == null) {
|
if (snippets == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final result = await Providers.server.runSnippets(spi.id, snippets);
|
final result = await Pros.server.runSnippets(spi.id, snippets);
|
||||||
if (result != null && result.isNotEmpty) {
|
if (result != null && result.isNotEmpty) {
|
||||||
context.showRoundDialog(
|
context.showRoundDialog(
|
||||||
title: Text(l10n.result),
|
title: Text(l10n.result),
|
||||||
@@ -188,7 +188,7 @@ Future<void> _gotoSSH(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _checkClient(BuildContext context, String id) {
|
bool _checkClient(BuildContext context, String id) {
|
||||||
final server = Providers.server.pick(id: id);
|
final server = Pros.server.pick(id: id);
|
||||||
if (server == null || server.client == null) {
|
if (server == null || server.client == null) {
|
||||||
context.showSnackBar(l10n.waitConnection);
|
context.showSnackBar(l10n.waitConnection);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user