mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2026-02-15 04:34:34 +01:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cdfd86f70 | ||
|
|
e709262477 | ||
|
|
20d2817c8d | ||
|
|
a7269ee68a | ||
|
|
552e2738cd | ||
|
|
216d64e3eb | ||
|
|
f903a1da55 | ||
|
|
f2b1158eda | ||
|
|
1d797f8d16 | ||
|
|
9e503e4901 | ||
|
|
bf22309fe5 | ||
|
|
a3537a67c2 | ||
|
|
8ba888a5b3 | ||
|
|
fd2d59b1e0 | ||
|
|
5c2cc5e5e4 | ||
|
|
fe7ea04011 | ||
|
|
19baff5dde | ||
|
|
0ac6984576 | ||
|
|
de0eedb2cb | ||
|
|
30dddf462e | ||
|
|
abee470afc | ||
|
|
42d8505504 | ||
|
|
3c481cf61c | ||
|
|
55c96031f7 | ||
|
|
2ea061d324 | ||
|
|
2747df64dd | ||
|
|
9e3afe98a9 | ||
|
|
7732ce5dbb | ||
|
|
f1ae924724 | ||
|
|
4cc72328a7 | ||
|
|
43e32775a3 | ||
|
|
27e7653587 | ||
|
|
89050daf4e | ||
|
|
2cc52fcee6 | ||
|
|
3f3240040a | ||
|
|
350d248776 | ||
|
|
bb15f34615 | ||
|
|
58b950f814 | ||
|
|
c2e822f49d | ||
|
|
4c4153ef98 | ||
|
|
35b5d1ccb5 | ||
|
|
065fd15429 |
1189
.dart_tool/flutter_gen/gen_l10n/l10n.dart
Normal file
1189
.dart_tool/flutter_gen/gen_l10n/l10n.dart
Normal file
File diff suppressed because it is too large
Load Diff
581
.dart_tool/flutter_gen/gen_l10n/l10n_en.dart
Normal file
581
.dart_tool/flutter_gen/gen_l10n/l10n_en.dart
Normal file
@@ -0,0 +1,581 @@
|
||||
import 'l10n.dart';
|
||||
|
||||
/// The translations for English (`en`).
|
||||
class SEn extends S {
|
||||
SEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get about => 'About';
|
||||
|
||||
@override
|
||||
String get aboutThanks => '\nThanks to the following people who participated in the test.';
|
||||
|
||||
@override
|
||||
String get addAServer => 'add a server';
|
||||
|
||||
@override
|
||||
String get addOne => 'Add one';
|
||||
|
||||
@override
|
||||
String get addPrivateKey => 'Add private key';
|
||||
|
||||
@override
|
||||
String get alreadyLastDir => 'Already in last directory.';
|
||||
|
||||
@override
|
||||
String get appPrimaryColor => 'App primary color';
|
||||
|
||||
@override
|
||||
String get attention => 'Attention';
|
||||
|
||||
@override
|
||||
String get auto => 'Auto';
|
||||
|
||||
@override
|
||||
String get backDir => 'Back';
|
||||
|
||||
@override
|
||||
String get backup => 'Backup';
|
||||
|
||||
@override
|
||||
String get backupAndRestore => 'Backup and Restore';
|
||||
|
||||
@override
|
||||
String get backupTip => 'The exported data is simply encrypted. \nPlease keep it safe.';
|
||||
|
||||
@override
|
||||
String get backupVersionNotMatch => 'Backup version is not match.';
|
||||
|
||||
@override
|
||||
String get bgRun => 'Run in backgroud';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get choose => 'Choose';
|
||||
|
||||
@override
|
||||
String get chooseDestination => 'Choose destination';
|
||||
|
||||
@override
|
||||
String get chooseFontFile => 'Choose a font file';
|
||||
|
||||
@override
|
||||
String get choosePrivateKey => 'Choose private key';
|
||||
|
||||
@override
|
||||
String get clear => 'Clear';
|
||||
|
||||
@override
|
||||
String get clickSee => 'Click here';
|
||||
|
||||
@override
|
||||
String get close => 'Close';
|
||||
|
||||
@override
|
||||
String get cmd => 'Command';
|
||||
|
||||
@override
|
||||
String get containerStatus => 'Container status';
|
||||
|
||||
@override
|
||||
String get convert => 'Convert';
|
||||
|
||||
@override
|
||||
String get copy => 'Copy';
|
||||
|
||||
@override
|
||||
String get copyPath => 'Copy path';
|
||||
|
||||
@override
|
||||
String get createFile => 'Create file';
|
||||
|
||||
@override
|
||||
String get createFolder => 'Create folder';
|
||||
|
||||
@override
|
||||
String get currentMode => 'Current Mode';
|
||||
|
||||
@override
|
||||
String get dark => 'Dark';
|
||||
|
||||
@override
|
||||
String get debug => 'Debug';
|
||||
|
||||
@override
|
||||
String get decode => 'Decode';
|
||||
|
||||
@override
|
||||
String get delete => 'Delete';
|
||||
|
||||
@override
|
||||
String get disconnected => 'Disconnected';
|
||||
|
||||
@override
|
||||
String dl2Local(Object fileName) {
|
||||
return 'Download [$fileName] to local?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get dockerContainerName => 'Container name';
|
||||
|
||||
@override
|
||||
String get dockerEditHost => 'Edit DOCKER_HOST';
|
||||
|
||||
@override
|
||||
String get dockerEmptyRunningItems => 'No running container. \nIt may be that the env DOCKER_HOST is not read correctly. You can found it by running `echo \$DOCKER_HOST` in terminal.';
|
||||
|
||||
@override
|
||||
String get dockerImage => 'Image';
|
||||
|
||||
@override
|
||||
String dockerImagesFmt(Object count) {
|
||||
return '$count images';
|
||||
}
|
||||
|
||||
@override
|
||||
String get dockerNotInstalled => 'Docker not installed';
|
||||
|
||||
@override
|
||||
String dockerStatusRunningAndStoppedFmt(Object runningCount, Object stoppedCount) {
|
||||
return '$runningCount running, $stoppedCount container stopped.';
|
||||
}
|
||||
|
||||
@override
|
||||
String dockerStatusRunningFmt(Object count) {
|
||||
return '$count container running.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get download => 'Download';
|
||||
|
||||
@override
|
||||
String get downloadFinished => 'Download finished';
|
||||
|
||||
@override
|
||||
String downloadStatus(Object percent, Object size) {
|
||||
return '$percent% of $size';
|
||||
}
|
||||
|
||||
@override
|
||||
String get edit => 'Edit';
|
||||
|
||||
@override
|
||||
String get encode => 'Encode';
|
||||
|
||||
@override
|
||||
String get error => 'Error';
|
||||
|
||||
@override
|
||||
String get exampleName => 'Example name';
|
||||
|
||||
@override
|
||||
String get experimentalFeature => 'Experimental feature';
|
||||
|
||||
@override
|
||||
String get export => 'Export';
|
||||
|
||||
@override
|
||||
String get extraArgs => 'Extra args';
|
||||
|
||||
@override
|
||||
String get failed => 'Failed';
|
||||
|
||||
@override
|
||||
String get feedback => 'Feedback';
|
||||
|
||||
@override
|
||||
String get feedbackOnGithub => 'If you have any questions, please feedback on Github.';
|
||||
|
||||
@override
|
||||
String get fieldMustNotEmpty => 'These fields must not be empty.';
|
||||
|
||||
@override
|
||||
String fileNotExist(Object file) {
|
||||
return '$file not exist';
|
||||
}
|
||||
|
||||
@override
|
||||
String fileTooLarge(Object file, Object size, Object sizeMax) {
|
||||
return 'File \'$file\' too large $size, max $sizeMax';
|
||||
}
|
||||
|
||||
@override
|
||||
String get files => 'Files';
|
||||
|
||||
@override
|
||||
String foundNUpdate(Object count) {
|
||||
return 'Found $count update';
|
||||
}
|
||||
|
||||
@override
|
||||
String get getPushTokenFailed => 'Can\'t fetch push token';
|
||||
|
||||
@override
|
||||
String get gettingToken => 'Getting token...';
|
||||
|
||||
@override
|
||||
String get go => 'Go';
|
||||
|
||||
@override
|
||||
String get goto => 'Go to';
|
||||
|
||||
@override
|
||||
String get host => 'Host';
|
||||
|
||||
@override
|
||||
String httpFailedWithCode(Object code) {
|
||||
return 'request failed, status code: $code';
|
||||
}
|
||||
|
||||
@override
|
||||
String get imagesList => 'Images list';
|
||||
|
||||
@override
|
||||
String get import => 'Import';
|
||||
|
||||
@override
|
||||
String get inputDomainHere => 'Input Domain here';
|
||||
|
||||
@override
|
||||
String get install => 'install';
|
||||
|
||||
@override
|
||||
String get installDockerWithUrl => 'Please https://docs.docker.com/engine/install docker first.';
|
||||
|
||||
@override
|
||||
String get invalidJson => 'Invalid JSON';
|
||||
|
||||
@override
|
||||
String get invalidVersion => 'Invalid version';
|
||||
|
||||
@override
|
||||
String invalidVersionHelp(Object url) {
|
||||
return 'Please make sure that docker is installed correctly, or that you are using a non-self-compiled version. If you don\'t have the above issues, please submit an issue on $url.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get isBusy => 'Is busy now';
|
||||
|
||||
@override
|
||||
String get keepForeground => 'Keep app foreground!';
|
||||
|
||||
@override
|
||||
String get keyAuth => 'Key Auth';
|
||||
|
||||
@override
|
||||
String get lastTry => 'Last try!';
|
||||
|
||||
@override
|
||||
String get launchPage => 'Launch page';
|
||||
|
||||
@override
|
||||
String get license => 'License';
|
||||
|
||||
@override
|
||||
String get light => 'Light';
|
||||
|
||||
@override
|
||||
String get loadingFiles => 'Loading files...';
|
||||
|
||||
@override
|
||||
String get loss => 'loss';
|
||||
|
||||
@override
|
||||
String madeWithLove(Object myGithub) {
|
||||
return 'Made with ❤️ by $myGithub';
|
||||
}
|
||||
|
||||
@override
|
||||
String get max => 'max';
|
||||
|
||||
@override
|
||||
String get maxRetryCount => 'Number of server reconnection';
|
||||
|
||||
@override
|
||||
String get maxRetryCountEqual0 => 'Will retry again and again.';
|
||||
|
||||
@override
|
||||
String get min => 'min';
|
||||
|
||||
@override
|
||||
String get ms => 'ms';
|
||||
|
||||
@override
|
||||
String get name => 'Name';
|
||||
|
||||
@override
|
||||
String get needRestart => 'Need to restart app';
|
||||
|
||||
@override
|
||||
String get newContainer => 'New container';
|
||||
|
||||
@override
|
||||
String get noClient => 'No client';
|
||||
|
||||
@override
|
||||
String get noInterface => 'No interface';
|
||||
|
||||
@override
|
||||
String get noResult => 'No result';
|
||||
|
||||
@override
|
||||
String get noSavedPrivateKey => 'No saved private keys.';
|
||||
|
||||
@override
|
||||
String get noSavedSnippet => 'No saved snippets.';
|
||||
|
||||
@override
|
||||
String get noServerAvailable => 'No server available.';
|
||||
|
||||
@override
|
||||
String get noUpdateAvailable => 'No update available';
|
||||
|
||||
@override
|
||||
String get notSelected => 'Not selected';
|
||||
|
||||
@override
|
||||
String get nullToken => 'Null token';
|
||||
|
||||
@override
|
||||
String get ok => 'OK';
|
||||
|
||||
@override
|
||||
String get onServerDetailPage => 'On server detail page';
|
||||
|
||||
@override
|
||||
String get onlyIOS => 'Only valid on iOS';
|
||||
|
||||
@override
|
||||
String get open => 'Open';
|
||||
|
||||
@override
|
||||
String get paste => 'Paste';
|
||||
|
||||
@override
|
||||
String get path => 'Path';
|
||||
|
||||
@override
|
||||
String get pickFile => 'Pick file';
|
||||
|
||||
@override
|
||||
String get ping => 'Ping';
|
||||
|
||||
@override
|
||||
String get pingAvg => 'Avg:';
|
||||
|
||||
@override
|
||||
String get pingInputIP => 'Please input a target IP/domain.';
|
||||
|
||||
@override
|
||||
String get pingNoServer => 'No server to ping.\nPlease add a server in server tab.';
|
||||
|
||||
@override
|
||||
String get pkg => 'Pkg';
|
||||
|
||||
@override
|
||||
String get platformNotSupportUpdate => 'Current platform does not support in app update.\nPlease build from source and install it.';
|
||||
|
||||
@override
|
||||
String get plzEnterHost => 'Please enter host.';
|
||||
|
||||
@override
|
||||
String get plzSelectKey => 'Please select a key.';
|
||||
|
||||
@override
|
||||
String get port => 'Port';
|
||||
|
||||
@override
|
||||
String get preview => 'Preview';
|
||||
|
||||
@override
|
||||
String get privateKey => 'Private Key';
|
||||
|
||||
@override
|
||||
String get pushToken => 'Push token';
|
||||
|
||||
@override
|
||||
String get pwd => 'Password';
|
||||
|
||||
@override
|
||||
String get rename => 'Rename';
|
||||
|
||||
@override
|
||||
String reportBugsOnGithubIssue(Object url) {
|
||||
return 'Please report bugs on $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get restart => 'Restart';
|
||||
|
||||
@override
|
||||
String get restore => 'Restore';
|
||||
|
||||
@override
|
||||
String get restoreSuccess => 'Restore success. Restart app to apply.';
|
||||
|
||||
@override
|
||||
String restoreSureWithDate(Object date) {
|
||||
return 'Are you sure to restore from $date ?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get result => 'Result';
|
||||
|
||||
@override
|
||||
String get run => 'Run';
|
||||
|
||||
@override
|
||||
String get save => 'Save';
|
||||
|
||||
@override
|
||||
String get second => 's';
|
||||
|
||||
@override
|
||||
String get server => 'Server';
|
||||
|
||||
@override
|
||||
String get serverTabConnecting => 'Connecting...';
|
||||
|
||||
@override
|
||||
String get serverTabEmpty => 'There is no server.\nClick the fab to add one.';
|
||||
|
||||
@override
|
||||
String get serverTabFailed => 'Failed';
|
||||
|
||||
@override
|
||||
String get serverTabLoading => 'Loading...';
|
||||
|
||||
@override
|
||||
String get serverTabPlzSave => 'Please \'save\' this private key again.';
|
||||
|
||||
@override
|
||||
String get serverTabUnkown => 'Unknown state';
|
||||
|
||||
@override
|
||||
String get setting => 'Setting';
|
||||
|
||||
@override
|
||||
String get sftpDlPrepare => 'Preparing to connect...';
|
||||
|
||||
@override
|
||||
String get sftpNoDownloadTask => 'No download task.';
|
||||
|
||||
@override
|
||||
String get sftpSSHConnected => 'SFTP Connected';
|
||||
|
||||
@override
|
||||
String get showDistLogo => 'Show distribution logo';
|
||||
|
||||
@override
|
||||
String get snippet => 'Snippet';
|
||||
|
||||
@override
|
||||
String spentTime(Object time) {
|
||||
return 'Spent time: $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String sshTip(Object url) {
|
||||
return 'This function is now in the experimental stage.\n\nPlease report bugs on $url or join our development.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get start => 'Start';
|
||||
|
||||
@override
|
||||
String get stop => 'Stop';
|
||||
|
||||
@override
|
||||
String get success => 'Success';
|
||||
|
||||
@override
|
||||
String sureDelete(Object name) {
|
||||
return 'Are you sure to delete [$name]?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get sureDirEmpty => 'Make sure dir is empty.';
|
||||
|
||||
@override
|
||||
String get sureNoPwd => 'Are you sure to use no password?';
|
||||
|
||||
@override
|
||||
String sureToDeleteServer(Object server) {
|
||||
return 'Are you sure to delete server [$server]?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get termTheme => 'Terminal theme';
|
||||
|
||||
@override
|
||||
String get themeMode => 'Theme mode';
|
||||
|
||||
@override
|
||||
String get times => 'Times';
|
||||
|
||||
@override
|
||||
String get ttl => 'ttl';
|
||||
|
||||
@override
|
||||
String get unknown => 'unknown';
|
||||
|
||||
@override
|
||||
String get unknownError => 'Unknown error';
|
||||
|
||||
@override
|
||||
String get unkownConvertMode => 'Unknown convert mode';
|
||||
|
||||
@override
|
||||
String get update => 'Update';
|
||||
|
||||
@override
|
||||
String get updateAll => 'Update all';
|
||||
|
||||
@override
|
||||
String get updateIntervalEqual0 => 'You set to 0, will not update automatically.\nCan\'t calculate CPU status.';
|
||||
|
||||
@override
|
||||
String get updateServerStatusInterval => 'Server status update interval';
|
||||
|
||||
@override
|
||||
String updateTip(Object newest) {
|
||||
return 'Update: v1.0.$newest';
|
||||
}
|
||||
|
||||
@override
|
||||
String updateTipTooLow(Object newest) {
|
||||
return 'Current version is too low, please update to v1.0.$newest';
|
||||
}
|
||||
|
||||
@override
|
||||
String get upsideDown => 'Upside Down';
|
||||
|
||||
@override
|
||||
String get urlOrJson => 'URL or JSON';
|
||||
|
||||
@override
|
||||
String get user => 'User';
|
||||
|
||||
@override
|
||||
String versionHaveUpdate(Object build) {
|
||||
return 'Found: v1.0.$build, click to update';
|
||||
}
|
||||
|
||||
@override
|
||||
String versionUnknownUpdate(Object build) {
|
||||
return 'Current: v1.0.$build';
|
||||
}
|
||||
|
||||
@override
|
||||
String versionUpdated(Object build) {
|
||||
return 'Current: v1.0.$build, is up to date';
|
||||
}
|
||||
|
||||
@override
|
||||
String get waitConnection => 'Please wait for the connection to be established.';
|
||||
|
||||
@override
|
||||
String get willTakEeffectImmediately => 'Will take effect immediately';
|
||||
}
|
||||
581
.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart
Normal file
581
.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart
Normal file
@@ -0,0 +1,581 @@
|
||||
import 'l10n.dart';
|
||||
|
||||
/// The translations for Chinese (`zh`).
|
||||
class SZh extends S {
|
||||
SZh([String locale = 'zh']) : super(locale);
|
||||
|
||||
@override
|
||||
String get about => '关于';
|
||||
|
||||
@override
|
||||
String get aboutThanks => '\n感谢以下参与软件测试的各位。';
|
||||
|
||||
@override
|
||||
String get addAServer => '添加服务器';
|
||||
|
||||
@override
|
||||
String get addOne => '前去新增';
|
||||
|
||||
@override
|
||||
String get addPrivateKey => '添加一个私钥';
|
||||
|
||||
@override
|
||||
String get alreadyLastDir => '已经是最上层目录了';
|
||||
|
||||
@override
|
||||
String get appPrimaryColor => 'App主要色';
|
||||
|
||||
@override
|
||||
String get attention => '注意';
|
||||
|
||||
@override
|
||||
String get auto => '自动';
|
||||
|
||||
@override
|
||||
String get backDir => '返回上一级';
|
||||
|
||||
@override
|
||||
String get backup => '备份';
|
||||
|
||||
@override
|
||||
String get backupAndRestore => '备份和恢复';
|
||||
|
||||
@override
|
||||
String get backupTip => '导出的数据仅进行了简单加密,请妥善保管。';
|
||||
|
||||
@override
|
||||
String get backupVersionNotMatch => '备份版本不匹配,无法恢复';
|
||||
|
||||
@override
|
||||
String get bgRun => '后台运行';
|
||||
|
||||
@override
|
||||
String get cancel => '取消';
|
||||
|
||||
@override
|
||||
String get choose => '选择';
|
||||
|
||||
@override
|
||||
String get chooseDestination => '选择目标';
|
||||
|
||||
@override
|
||||
String get chooseFontFile => '选择字体文件';
|
||||
|
||||
@override
|
||||
String get choosePrivateKey => '选择私钥';
|
||||
|
||||
@override
|
||||
String get clear => '清除';
|
||||
|
||||
@override
|
||||
String get clickSee => '点击查看';
|
||||
|
||||
@override
|
||||
String get close => '关闭';
|
||||
|
||||
@override
|
||||
String get cmd => '命令';
|
||||
|
||||
@override
|
||||
String get containerStatus => '容器状态';
|
||||
|
||||
@override
|
||||
String get convert => '转换';
|
||||
|
||||
@override
|
||||
String get copy => '复制';
|
||||
|
||||
@override
|
||||
String get copyPath => '复制路径';
|
||||
|
||||
@override
|
||||
String get createFile => '创建文件';
|
||||
|
||||
@override
|
||||
String get createFolder => '创建文件夹';
|
||||
|
||||
@override
|
||||
String get currentMode => '当前模式';
|
||||
|
||||
@override
|
||||
String get dark => '暗';
|
||||
|
||||
@override
|
||||
String get debug => '调试';
|
||||
|
||||
@override
|
||||
String get decode => '解码';
|
||||
|
||||
@override
|
||||
String get delete => '删除';
|
||||
|
||||
@override
|
||||
String get disconnected => '连接断开';
|
||||
|
||||
@override
|
||||
String dl2Local(Object fileName) {
|
||||
return '下载 [$fileName] 到本地?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get dockerContainerName => '容器名';
|
||||
|
||||
@override
|
||||
String get dockerEditHost => '编辑 DOCKER_HOST';
|
||||
|
||||
@override
|
||||
String get dockerEmptyRunningItems => '没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo \$DOCKER_HOST` 来获取。';
|
||||
|
||||
@override
|
||||
String get dockerImage => '镜像';
|
||||
|
||||
@override
|
||||
String dockerImagesFmt(Object count) {
|
||||
return '共 $count 个镜像';
|
||||
}
|
||||
|
||||
@override
|
||||
String get dockerNotInstalled => 'Docker未安装';
|
||||
|
||||
@override
|
||||
String dockerStatusRunningAndStoppedFmt(Object runningCount, Object stoppedCount) {
|
||||
return '$runningCount个正在运行, $stoppedCount个已停止';
|
||||
}
|
||||
|
||||
@override
|
||||
String dockerStatusRunningFmt(Object count) {
|
||||
return '$count个容器正在运行';
|
||||
}
|
||||
|
||||
@override
|
||||
String get download => '下载';
|
||||
|
||||
@override
|
||||
String get downloadFinished => '下载完成!';
|
||||
|
||||
@override
|
||||
String downloadStatus(Object percent, Object size) {
|
||||
return '$size 的 $percent%';
|
||||
}
|
||||
|
||||
@override
|
||||
String get edit => '编辑';
|
||||
|
||||
@override
|
||||
String get encode => '编码';
|
||||
|
||||
@override
|
||||
String get error => '出错了';
|
||||
|
||||
@override
|
||||
String get exampleName => '名称示例';
|
||||
|
||||
@override
|
||||
String get experimentalFeature => '实验性功能';
|
||||
|
||||
@override
|
||||
String get export => '导出';
|
||||
|
||||
@override
|
||||
String get extraArgs => '额外参数';
|
||||
|
||||
@override
|
||||
String get failed => '失败';
|
||||
|
||||
@override
|
||||
String get feedback => '反馈';
|
||||
|
||||
@override
|
||||
String get feedbackOnGithub => '如果你有任何问题,请在GitHub反馈';
|
||||
|
||||
@override
|
||||
String get fieldMustNotEmpty => '这些输入框不能为空。';
|
||||
|
||||
@override
|
||||
String fileNotExist(Object file) {
|
||||
return '$file 不存在';
|
||||
}
|
||||
|
||||
@override
|
||||
String fileTooLarge(Object file, Object size, Object sizeMax) {
|
||||
return '文件 \'$file\' 过大 \'$size\',超过了 $sizeMax';
|
||||
}
|
||||
|
||||
@override
|
||||
String get files => '文件';
|
||||
|
||||
@override
|
||||
String foundNUpdate(Object count) {
|
||||
return '找到 $count 个更新';
|
||||
}
|
||||
|
||||
@override
|
||||
String get getPushTokenFailed => '未能获取到推送token';
|
||||
|
||||
@override
|
||||
String get gettingToken => '正在获取Token...';
|
||||
|
||||
@override
|
||||
String get go => '开始';
|
||||
|
||||
@override
|
||||
String get goto => '前往';
|
||||
|
||||
@override
|
||||
String get host => '主机';
|
||||
|
||||
@override
|
||||
String httpFailedWithCode(Object code) {
|
||||
return '请求失败, 状态码: $code';
|
||||
}
|
||||
|
||||
@override
|
||||
String get imagesList => '镜像列表';
|
||||
|
||||
@override
|
||||
String get import => '导入';
|
||||
|
||||
@override
|
||||
String get inputDomainHere => '在这里输入域名';
|
||||
|
||||
@override
|
||||
String get install => '安装';
|
||||
|
||||
@override
|
||||
String get installDockerWithUrl => '请先 https://docs.docker.com/engine/install docker';
|
||||
|
||||
@override
|
||||
String get invalidJson => '无效的json,存在格式问题';
|
||||
|
||||
@override
|
||||
String get invalidVersion => '不支持的版本';
|
||||
|
||||
@override
|
||||
String invalidVersionHelp(Object url) {
|
||||
return '请确保正确安装了docker,或者使用的非自编译版本。如果没有以上问题,请在 $url 提交问题。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get isBusy => '当前正忙';
|
||||
|
||||
@override
|
||||
String get keepForeground => '请保持应用处于前台!';
|
||||
|
||||
@override
|
||||
String get keyAuth => '公钥认证';
|
||||
|
||||
@override
|
||||
String get lastTry => '最后尝试';
|
||||
|
||||
@override
|
||||
String get launchPage => '启动页';
|
||||
|
||||
@override
|
||||
String get license => '开源证书';
|
||||
|
||||
@override
|
||||
String get light => '亮';
|
||||
|
||||
@override
|
||||
String get loadingFiles => '正在加载目录。。。';
|
||||
|
||||
@override
|
||||
String get loss => '丢包率';
|
||||
|
||||
@override
|
||||
String madeWithLove(Object myGithub) {
|
||||
return '用❤️制作 by $myGithub';
|
||||
}
|
||||
|
||||
@override
|
||||
String get max => '最大';
|
||||
|
||||
@override
|
||||
String get maxRetryCount => '服务器尝试重连次数';
|
||||
|
||||
@override
|
||||
String get maxRetryCountEqual0 => '会无限重试';
|
||||
|
||||
@override
|
||||
String get min => '最小';
|
||||
|
||||
@override
|
||||
String get ms => '毫秒';
|
||||
|
||||
@override
|
||||
String get name => '名称';
|
||||
|
||||
@override
|
||||
String get needRestart => '需要重启 App';
|
||||
|
||||
@override
|
||||
String get newContainer => '新建容器';
|
||||
|
||||
@override
|
||||
String get noClient => '没有SSH连接';
|
||||
|
||||
@override
|
||||
String get noInterface => '没有可用的接口';
|
||||
|
||||
@override
|
||||
String get noResult => '无结果';
|
||||
|
||||
@override
|
||||
String get noSavedPrivateKey => '没有已保存的私钥。';
|
||||
|
||||
@override
|
||||
String get noSavedSnippet => '没有已保存的代码片段。';
|
||||
|
||||
@override
|
||||
String get noServerAvailable => '没有可用的服务器。';
|
||||
|
||||
@override
|
||||
String get noUpdateAvailable => '没有可用更新';
|
||||
|
||||
@override
|
||||
String get notSelected => '未选择';
|
||||
|
||||
@override
|
||||
String get nullToken => '无Token';
|
||||
|
||||
@override
|
||||
String get ok => '好';
|
||||
|
||||
@override
|
||||
String get onServerDetailPage => '在服务器详情页';
|
||||
|
||||
@override
|
||||
String get onlyIOS => '仅在iOS上有效';
|
||||
|
||||
@override
|
||||
String get open => '打开';
|
||||
|
||||
@override
|
||||
String get paste => '粘贴';
|
||||
|
||||
@override
|
||||
String get path => '路径';
|
||||
|
||||
@override
|
||||
String get pickFile => '选择文件';
|
||||
|
||||
@override
|
||||
String get ping => 'Ping';
|
||||
|
||||
@override
|
||||
String get pingAvg => '平均:';
|
||||
|
||||
@override
|
||||
String get pingInputIP => '请输入目标IP或域名';
|
||||
|
||||
@override
|
||||
String get pingNoServer => '没有服务器可用于Ping\n请在服务器tab添加服务器后再试';
|
||||
|
||||
@override
|
||||
String get pkg => '包管理';
|
||||
|
||||
@override
|
||||
String get platformNotSupportUpdate => '当前平台不支持更新,请编译最新源码后手动安装';
|
||||
|
||||
@override
|
||||
String get plzEnterHost => '请输入主机';
|
||||
|
||||
@override
|
||||
String get plzSelectKey => '请选择私钥';
|
||||
|
||||
@override
|
||||
String get port => '端口';
|
||||
|
||||
@override
|
||||
String get preview => '预览';
|
||||
|
||||
@override
|
||||
String get privateKey => '私钥';
|
||||
|
||||
@override
|
||||
String get pushToken => '消息推送 Token';
|
||||
|
||||
@override
|
||||
String get pwd => '密码';
|
||||
|
||||
@override
|
||||
String get rename => '重命名';
|
||||
|
||||
@override
|
||||
String reportBugsOnGithubIssue(Object url) {
|
||||
return '请到 $url 提交问题';
|
||||
}
|
||||
|
||||
@override
|
||||
String get restart => '重启';
|
||||
|
||||
@override
|
||||
String get restore => '恢复';
|
||||
|
||||
@override
|
||||
String get restoreSuccess => '恢复成功,需要重启App来应用更改';
|
||||
|
||||
@override
|
||||
String restoreSureWithDate(Object date) {
|
||||
return '确定恢复 $date 的备份吗?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get result => '结果';
|
||||
|
||||
@override
|
||||
String get run => '运行';
|
||||
|
||||
@override
|
||||
String get save => '保存';
|
||||
|
||||
@override
|
||||
String get second => '秒';
|
||||
|
||||
@override
|
||||
String get server => '服务器';
|
||||
|
||||
@override
|
||||
String get serverTabConnecting => '连接中...';
|
||||
|
||||
@override
|
||||
String get serverTabEmpty => '现在没有服务器。\n点击右下方按钮来添加。';
|
||||
|
||||
@override
|
||||
String get serverTabFailed => '失败';
|
||||
|
||||
@override
|
||||
String get serverTabLoading => '加载中...';
|
||||
|
||||
@override
|
||||
String get serverTabPlzSave => '请再次保存该私钥';
|
||||
|
||||
@override
|
||||
String get serverTabUnkown => '未知状态';
|
||||
|
||||
@override
|
||||
String get setting => '设置';
|
||||
|
||||
@override
|
||||
String get sftpDlPrepare => '准备连接至服务器...';
|
||||
|
||||
@override
|
||||
String get sftpNoDownloadTask => '没有下载任务';
|
||||
|
||||
@override
|
||||
String get sftpSSHConnected => 'SFTP 已连接,即将开始下载...';
|
||||
|
||||
@override
|
||||
String get showDistLogo => '显示发行版 Logo';
|
||||
|
||||
@override
|
||||
String get snippet => '代码片段';
|
||||
|
||||
@override
|
||||
String spentTime(Object time) {
|
||||
return '耗时: $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String sshTip(Object url) {
|
||||
return '该功能目前处于测试阶段。\n\n请在 $url 反馈问题,或者加入我们开发。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get start => '开始';
|
||||
|
||||
@override
|
||||
String get stop => '停止';
|
||||
|
||||
@override
|
||||
String get success => '成功';
|
||||
|
||||
@override
|
||||
String sureDelete(Object name) {
|
||||
return '确定删除[$name]?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get sureDirEmpty => '请确保文件夹为空';
|
||||
|
||||
@override
|
||||
String get sureNoPwd => '确认使用无密码?';
|
||||
|
||||
@override
|
||||
String sureToDeleteServer(Object server) {
|
||||
return '你确定要删除服务器 [$server] 吗?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get termTheme => '终端主题';
|
||||
|
||||
@override
|
||||
String get themeMode => '主题模式';
|
||||
|
||||
@override
|
||||
String get times => '次';
|
||||
|
||||
@override
|
||||
String get ttl => '缓存时间';
|
||||
|
||||
@override
|
||||
String get unknown => '未知';
|
||||
|
||||
@override
|
||||
String get unknownError => '未知错误';
|
||||
|
||||
@override
|
||||
String get unkownConvertMode => '未知转换模式';
|
||||
|
||||
@override
|
||||
String get update => '更新';
|
||||
|
||||
@override
|
||||
String get updateAll => '更新全部';
|
||||
|
||||
@override
|
||||
String get updateIntervalEqual0 => '你设置为0,服务器状态不会自动刷新。\n且不能计算CPU使用情况。';
|
||||
|
||||
@override
|
||||
String get updateServerStatusInterval => '服务器状态刷新间隔';
|
||||
|
||||
@override
|
||||
String updateTip(Object newest) {
|
||||
return '新版本: v1.0.$newest';
|
||||
}
|
||||
|
||||
@override
|
||||
String updateTipTooLow(Object newest) {
|
||||
return '当前版本过低,请升级至 v1.0.$newest';
|
||||
}
|
||||
|
||||
@override
|
||||
String get upsideDown => '上下交换';
|
||||
|
||||
@override
|
||||
String get urlOrJson => '链接或JSON';
|
||||
|
||||
@override
|
||||
String get user => '用户';
|
||||
|
||||
@override
|
||||
String versionHaveUpdate(Object build) {
|
||||
return '找到新版本:v1.0.$build, 点击更新';
|
||||
}
|
||||
|
||||
@override
|
||||
String versionUnknownUpdate(Object build) {
|
||||
return '当前:v1.0.$build';
|
||||
}
|
||||
|
||||
@override
|
||||
String versionUpdated(Object build) {
|
||||
return '当前:v1.0.$build, 已是最新版本';
|
||||
}
|
||||
|
||||
@override
|
||||
String get waitConnection => '请等待连接建立';
|
||||
|
||||
@override
|
||||
String get willTakEeffectImmediately => '更改将会立即生效';
|
||||
}
|
||||
3
.dart_tool/flutter_gen/pubspec.yaml
Normal file
3
.dart_tool/flutter_gen/pubspec.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Generated by the flutter tool
|
||||
name: synthetic_package
|
||||
description: The Flutter application's synthetic package.
|
||||
20
.github/workflows/issue-translator.yml
vendored
Normal file
20
.github/workflows/issue-translator.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 'issue-translator'
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: usthe/issues-translate-action@v2.7
|
||||
with:
|
||||
# not require, default false.
|
||||
# Decide whether to modify the issue title.
|
||||
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
|
||||
IS_MODIFY_TITLE: false
|
||||
# not require.
|
||||
# Customize the translation robot prefix message.
|
||||
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -23,7 +23,7 @@
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
# .dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
@@ -48,4 +48,8 @@ app.*.map.json
|
||||
/android/app/fjy.androidstudio.key
|
||||
/release
|
||||
test.dart
|
||||
.fvm
|
||||
.fvm
|
||||
|
||||
# Keep generated l10n files
|
||||
/.dart_tool/*
|
||||
!/.dart_tool/flutter_gen
|
||||
38
README.md
38
README.md
@@ -1,3 +1,4 @@
|
||||
English | [简体中文](README_zh.md)
|
||||
<!-- Title-->
|
||||
<p align="center">
|
||||
<h1 align="center">Server Box</h1>
|
||||
@@ -28,18 +29,25 @@ Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartss
|
||||
|
||||
## 🔖 Feature
|
||||
- [x] Functions
|
||||
- [x] `SSH` Terminal
|
||||
- [x] `SSH` Terminal, `SFTP`
|
||||
- [x] `Docker & Pkg` Manager
|
||||
- [x] `SFTP`
|
||||
- [x] `Snippet`
|
||||
- [x] `Ping`
|
||||
- [x] Status charts
|
||||
- [x] etc.
|
||||
- [x] `Ping` and etc.
|
||||
- [x] i18n (English, Chinese)
|
||||
- **Welcome contribution** :)
|
||||
- [How to contribute?](#i18n-guide)
|
||||
- Welcome contribution :), [How to contribute?](#l10n-guide)
|
||||
- [x] Desktop support
|
||||
|
||||
|
||||
## 📩 Push
|
||||
You need to install [ServerBoxMonitor](https://github.com/lollipopkit/server_box_monitor) on your servers.
|
||||
And config `iOS / Webhook / ServerChan` to push server status to your portable device without using ServerBox app.
|
||||
|
||||
|
||||
## 🆘 Help
|
||||
If you have any question or feature request, please open a [discussion](https://github.com/lollipopkit/flutter_server_box/discussions/new/choose).
|
||||
If ServerBox app has any bug, please open an [issue](https://github.com/lollipopkit/flutter_server_box/issues/new).
|
||||
|
||||
|
||||
## 📱 ScreenShots
|
||||
<table>
|
||||
<tr>
|
||||
@@ -74,19 +82,23 @@ Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartss
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## 🖥 Platform
|
||||
Status|Platform
|
||||
--- | ---
|
||||
Full Support|Android/iOS
|
||||
Support, but not tested|macOS/Windows/Linux
|
||||
Full Support| Android / iOS / macOS
|
||||
Support, but not tested| Windows / Linux
|
||||
|
||||
## i18n guide
|
||||
1. Fork this repo and clone it to your local machine.
|
||||
|
||||
## l10n guide
|
||||
1. Fork this repo and clone forked repo to your local machine.
|
||||
2. Create `arb` file in `lib/l10n/` directory
|
||||
- File name should be `intl_XX.arb`, where `XX` is the language code. Such as `intl_en.arb` for English and `intl_zh.arb` for Chinese.
|
||||
3. Add content to the file. You can refer to `intl_en.arb` and `intl_zh.arb` for the format.
|
||||
4. Pull commit to your forked repo.
|
||||
5. Request a pull request on my repo.
|
||||
4. Run `flutter gen-l10n` to generate files.
|
||||
5. Pull commit to your forked repo.
|
||||
6. Request a pull request on my repo.
|
||||
|
||||
|
||||
## 📝 License
|
||||
`GPL v3. lollipopkit 2023`
|
||||
|
||||
104
README_zh.md
Normal file
104
README_zh.md
Normal file
@@ -0,0 +1,104 @@
|
||||
简体中文 | [English](README.md)
|
||||
<!-- Title-->
|
||||
<p align="center">
|
||||
<h1 align="center">Server Box</h1>
|
||||
</p>
|
||||
|
||||
<!-- Badges-->
|
||||
<p align="center">
|
||||
<a href="https://apps.apple.com/app/id1586449703">
|
||||
<img style="height: 37px" src="screenshots/appstore.svg">
|
||||
</a>
|
||||
<a href="https://github.com/lollipopkit/flutter_server_box/releases/latest">
|
||||
<img style="height: 37px" src="screenshots/dl-android.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://count.ly/f/badge" rel="nofollow">
|
||||
<img style="height: 37px" src="https://count.ly/badges/light.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
使用Flutter开发的服务器工具箱,提供服务器状态图表和管理工具。
|
||||
<br>
|
||||
特别感谢 <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
|
||||
</p>
|
||||
|
||||
|
||||
## 🔖 特点
|
||||
- [x] 功能
|
||||
- [x] `SSH` 终端, `SFTP`
|
||||
- [x] `Docker & 包` 管理器
|
||||
- [x] 状态图表
|
||||
- [x] `Ping` 和 更多
|
||||
- [x] 本地化 ( 英语, 中文 )
|
||||
- 欢迎贡献 :),[怎么贡献?](#l10n)
|
||||
- [x] 桌面端支持
|
||||
|
||||
|
||||
## 📩 推送
|
||||
你需要在你的服务器上安装 [ServerBoxMonitor](https://github.com/lollipopkit/server_box_monitor)。
|
||||
并且配置 `iOS / Webhook / Server酱` 推送服务,这样,你可以在不使用 ServerBox app 时获取服务器状态。
|
||||
|
||||
|
||||
## 🆘 帮助
|
||||
如果你有任何问题或者功能请求,请在 [讨论](https://github.com/lollipopkit/flutter_server_box/discussions/new/choose) 中交流。
|
||||
如果 ServerBox app 有任何 bug,请在 [问题](https://github.com/lollipopkit/flutter_server_box/issues/new) 中反馈。
|
||||
|
||||
|
||||
## 📱 截屏
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/server.jpg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/detail.jpg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/ssh.jpg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/apt.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/ping.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/sftp.jpg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/docker.jpg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="screenshots/convert.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## 🖥 平台
|
||||
状态|平台
|
||||
--- | ---
|
||||
完整支持 | Android / iOS / macOS
|
||||
可能支持,未测试 | Windows / Linux
|
||||
|
||||
|
||||
## l10n
|
||||
1. Fork本项目,并Clone你Fork的项目至你的电脑
|
||||
2. 在 `lib/l10n/` 文件夹内创建 `.arb` 本地化文件
|
||||
- 文件名应该类似 `intl_XX.arb`, `XX` 是语言标识码。 例如 `intl_en.arb` 是给英语的, `intl_zh.arb` 是给中文的
|
||||
3. 向 `.arb` 本地化文件添加内容。 你可以查看 `intl_en.arb` 和 `intl_zh.arb` 的内容,并理解其含义,来创建新的本地化文件
|
||||
4. 运行 `flutter gen-l10n` 来生成所需文件
|
||||
5. Commit 变更到你的 Fork 的 Repo
|
||||
6. 在我的项目中发起 Pull Request.
|
||||
|
||||
|
||||
## 📝 License
|
||||
`GPL v3. lollipopkit 2023`
|
||||
@@ -1,6 +1,23 @@
|
||||
package tech.lolli.toolbox
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
class MainActivity : FlutterActivity() {
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
val binaryMessenger = flutterEngine.dartExecutor.binaryMessenger
|
||||
|
||||
MethodChannel(binaryMessenger, "tech.lolli.toolbox/app_retain").apply {
|
||||
setMethodCallHandler { method, result ->
|
||||
if (method.method == "sendToBackground") {
|
||||
moveTaskToBack(true)
|
||||
result.success(null)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -9,6 +9,8 @@ PODS:
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- plain_notification_token (0.0.1):
|
||||
- Flutter
|
||||
- r_upgrade (0.0.1):
|
||||
- Flutter
|
||||
- share_plus (0.0.1):
|
||||
@@ -22,6 +24,7 @@ DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||
- plain_notification_token (from `.symlinks/plugins/plain_notification_token/ios`)
|
||||
- r_upgrade (from `.symlinks/plugins/r_upgrade/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
@@ -37,6 +40,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||
plain_notification_token:
|
||||
:path: ".symlinks/plugins/plain_notification_token/ios"
|
||||
r_upgrade:
|
||||
:path: ".symlinks/plugins/r_upgrade/ios"
|
||||
share_plus:
|
||||
@@ -50,6 +55,7 @@ SPEC CHECKSUMS:
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
|
||||
plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1
|
||||
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9C5314B89F1F73A1900CCAFD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
A775F241DEE026555178AC01 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
E398BF6A29BDB34500FE4FD5 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -104,6 +105,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E398BF6A29BDB34500FE4FD5 /* Runner.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
@@ -356,15 +358,16 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 227;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 269;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.227;
|
||||
MARKETING_VERSION = 1.0.269;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -486,15 +489,16 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 227;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 269;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.227;
|
||||
MARKETING_VERSION = 1.0.269;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -510,15 +514,16 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 227;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 269;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.227;
|
||||
MARKETING_VERSION = 1.0.269;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
68
ios/Runner/Info-Debug.plist
Normal file
68
ios/Runner/Info-Debug.plist
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
<string>zh</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>ServerBox</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_dartobservatory._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>ServerBox needs to access your local network to discover and connect to your server.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
68
ios/Runner/Info-Profile.plist
Normal file
68
ios/Runner/Info-Profile.plist
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
<string>zh</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>ServerBox</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_dartobservatory._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>ServerBox needs to access your local network to discover and connect to your server.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
62
ios/Runner/Info-Release.plist
Normal file
62
ios/Runner/Info-Release.plist
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
<string>zh</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>ServerBox</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>ServerBox</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
<string>zh</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
8
ios/Runner/Runner.entitlements
Normal file
8
ios/Runner/Runner.entitlements
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>production</string>
|
||||
</dict>
|
||||
</plist>
|
||||
File diff suppressed because one or more lines are too long
95
lib/app.dart
95
lib/app.dart
@@ -17,19 +17,8 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setTransparentNavigationBar(context);
|
||||
return ValueListenableBuilder<int>(
|
||||
valueListenable: _setting.primaryColor.listenable(),
|
||||
builder: (_, colorValue, __) {
|
||||
primaryColor = Color(colorValue);
|
||||
return ValueListenableBuilder<int>(
|
||||
valueListenable: _setting.nightMode.listenable(),
|
||||
builder: (_, mode, __) => _buildApp(mode),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
primaryColor = Color(_setting.primaryColor.fetch()!);
|
||||
|
||||
Widget _buildApp(int nightMode) {
|
||||
final textStyle = TextStyle(color: primaryColor);
|
||||
final materialColor = primaryColor.materialStateColor;
|
||||
final materialColorAlpha = primaryColor.withOpacity(0.7).materialStateColor;
|
||||
@@ -50,43 +39,55 @@ class MyApp extends StatelessWidget {
|
||||
final radioTheme = RadioThemeData(
|
||||
fillColor: materialColor,
|
||||
);
|
||||
final ok = nightMode >= 0 && nightMode <= ThemeMode.values.length - 1;
|
||||
final themeMode = ok ? ThemeMode.values[nightMode] : ThemeMode.system;
|
||||
final primarySwatch = primaryColor.materialColor;
|
||||
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: S.localizationsDelegates,
|
||||
supportedLocales: S.supportedLocales,
|
||||
title: BuildData.name,
|
||||
themeMode: themeMode,
|
||||
theme: ThemeData(
|
||||
useMaterial3: false,
|
||||
primaryColor: primaryColor,
|
||||
primarySwatch: primaryColor.materialColor,
|
||||
appBarTheme: appBarTheme,
|
||||
floatingActionButtonTheme: fabTheme,
|
||||
iconTheme: iconTheme,
|
||||
primaryIconTheme: iconTheme,
|
||||
switchTheme: switchTheme,
|
||||
inputDecorationTheme: inputDecorationTheme,
|
||||
radioTheme: radioTheme,
|
||||
),
|
||||
darkTheme: ThemeData.dark().copyWith(
|
||||
useMaterial3: false,
|
||||
floatingActionButtonTheme: fabTheme,
|
||||
iconTheme: iconTheme,
|
||||
primaryIconTheme: iconTheme,
|
||||
switchTheme: switchTheme,
|
||||
inputDecorationTheme: inputDecorationTheme,
|
||||
radioTheme: radioTheme,
|
||||
colorScheme: ColorScheme.fromSwatch(
|
||||
primarySwatch: primaryColor.materialColor,
|
||||
return ValueListenableBuilder<int>(
|
||||
valueListenable: _setting.themeMode.listenable(),
|
||||
builder: (_, tMode, __) {
|
||||
final ok = tMode >= 0 && tMode <= ThemeMode.values.length - 1;
|
||||
final themeMode = ok ? ThemeMode.values[tMode] : ThemeMode.system;
|
||||
|
||||
final theme = ThemeData(
|
||||
useMaterial3: false,
|
||||
primaryColor: primaryColor,
|
||||
primarySwatch: primarySwatch,
|
||||
appBarTheme: appBarTheme,
|
||||
floatingActionButtonTheme: fabTheme,
|
||||
iconTheme: iconTheme,
|
||||
primaryIconTheme: iconTheme,
|
||||
switchTheme: switchTheme,
|
||||
inputDecorationTheme: inputDecorationTheme,
|
||||
radioTheme: radioTheme,
|
||||
);
|
||||
final darkTheme = ThemeData(
|
||||
useMaterial3: false,
|
||||
primaryColor: primaryColor,
|
||||
primarySwatch: primarySwatch,
|
||||
floatingActionButtonTheme: fabTheme,
|
||||
iconTheme: iconTheme,
|
||||
primaryIconTheme: iconTheme,
|
||||
switchTheme: switchTheme,
|
||||
inputDecorationTheme: inputDecorationTheme,
|
||||
radioTheme: radioTheme,
|
||||
brightness: Brightness.dark,
|
||||
accentColor: primaryColor,
|
||||
),
|
||||
),
|
||||
themeAnimationDuration: const Duration(milliseconds: 237),
|
||||
home: const MyHomePage(),
|
||||
colorScheme: ColorScheme.fromSwatch(
|
||||
primarySwatch: primaryColor.materialColor,
|
||||
brightness: Brightness.dark,
|
||||
accentColor: primaryColor,
|
||||
),
|
||||
);
|
||||
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: S.localizationsDelegates,
|
||||
supportedLocales: S.supportedLocales,
|
||||
title: BuildData.name,
|
||||
themeMode: themeMode,
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,20 @@ import 'package:countly_flutter/countly_flutter.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:toolbox/core/build_mode.dart';
|
||||
|
||||
import 'utils/platform.dart';
|
||||
|
||||
class Analysis {
|
||||
static const _url = 'https://countly.xuty.cc';
|
||||
static const _key = '80372a2a66424b32d0ac8991bfa1ef058bd36b1f';
|
||||
|
||||
static bool _enabled = false;
|
||||
static bool enabled = false;
|
||||
|
||||
static Future<void> init() async {
|
||||
if (!BuildMode.isRelease) {
|
||||
return;
|
||||
}
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
_enabled = true;
|
||||
if (isAndroid || isIOS) {
|
||||
enabled = true;
|
||||
final config = CountlyConfig(_url, _key)
|
||||
.setLoggingEnabled(false)
|
||||
.enableCrashReporting();
|
||||
@@ -31,13 +33,13 @@ class Analysis {
|
||||
}
|
||||
|
||||
static void recordView(String view) {
|
||||
if (_enabled) {
|
||||
if (enabled) {
|
||||
Countly.recordView(view);
|
||||
}
|
||||
}
|
||||
|
||||
static void recordException(Object exception, [bool fatal = false]) {
|
||||
if (_enabled) {
|
||||
if (enabled) {
|
||||
Countly.logException(exception.toString(), !fatal, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||
|
||||
extension FutureUint8ListX on Future<Uint8List> {
|
||||
Future<String> get string async => utf8.decode(await this);
|
||||
Future<ByteData> get byteData async => (await this).buffer.asByteData();
|
||||
}
|
||||
|
||||
extension Uint8ListX on Uint8List {
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:r_upgrade/r_upgrade.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../data/model/app/update.dart';
|
||||
import '../data/provider/app.dart';
|
||||
import '../data/res/build_data.dart';
|
||||
import '../data/service/app.dart';
|
||||
import '../locator.dart';
|
||||
import 'utils/platform.dart';
|
||||
import 'utils/ui.dart';
|
||||
|
||||
final _logger = Logger('UPDATE');
|
||||
@@ -28,18 +27,13 @@ Future<bool> isFileAvailable(String url) async {
|
||||
Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
||||
final update = await locator<AppService>().getUpdate();
|
||||
|
||||
locator<AppProvider>().setNewestBuild(update.newest);
|
||||
final newest = update.build.last.current;
|
||||
if (newest == null) {
|
||||
_logger.warning('Update not available on $platform');
|
||||
return;
|
||||
}
|
||||
|
||||
final newest = () {
|
||||
if (Platform.isAndroid) {
|
||||
return update.androidbuild;
|
||||
} else if (Platform.isIOS) {
|
||||
return update.iosbuild;
|
||||
} else if (Platform.isMacOS) {
|
||||
return update.macbuild;
|
||||
}
|
||||
return update.newest;
|
||||
}();
|
||||
locator<AppProvider>().setNewestBuild(newest);
|
||||
|
||||
if (!force && newest <= BuildData.build) {
|
||||
_logger.info('Update ignored due to current: ${BuildData.build}, '
|
||||
@@ -48,21 +42,23 @@ Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
||||
}
|
||||
_logger.info('Update available: $newest');
|
||||
|
||||
if (Platform.isAndroid && !await isFileAvailable(update.android)) {
|
||||
final url = update.url.current!;
|
||||
|
||||
if (isAndroid && !await isFileAvailable(url)) {
|
||||
_logger.warning('Android update file not available');
|
||||
return;
|
||||
}
|
||||
|
||||
final s = S.of(context)!;
|
||||
|
||||
if (update.min > BuildData.build) {
|
||||
if (update.build.min.current! > BuildData.build) {
|
||||
showRoundDialog(
|
||||
context,
|
||||
s.attention,
|
||||
Text(s.updateTipTooLow(newest)),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => _doUpdate(update, context, s),
|
||||
onPressed: () => _doUpdate(url, context, s),
|
||||
child: Text(s.ok),
|
||||
)
|
||||
],
|
||||
@@ -72,25 +68,25 @@ Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
||||
|
||||
showSnackBarWithAction(
|
||||
context,
|
||||
'${s.updateTip(newest)} \n${update.changelog}',
|
||||
'${s.updateTip(newest)} \n${update.changelog.current}',
|
||||
s.update,
|
||||
() => _doUpdate(update, context, s),
|
||||
() => _doUpdate(url, context, s),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _doUpdate(AppUpdate update, BuildContext context, S s) async {
|
||||
if (Platform.isAndroid) {
|
||||
Future<void> _doUpdate(String url, BuildContext context, S s) async {
|
||||
if (isAndroid) {
|
||||
await RUpgrade.upgrade(
|
||||
update.android,
|
||||
fileName: update.android.split('/').last,
|
||||
url,
|
||||
fileName: url.split('/').last,
|
||||
isAutoRequestInstall: true,
|
||||
);
|
||||
} else if (Platform.isIOS) {
|
||||
} else if (isIOS) {
|
||||
await RUpgrade.upgradeFromAppStore('1586449703');
|
||||
} else {
|
||||
showRoundDialog(context, s.attention, Text(s.platformNotSupportUpdate), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(s.ok),
|
||||
)
|
||||
]);
|
||||
|
||||
@@ -4,8 +4,16 @@ import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:plain_notification_token/plain_notification_token.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../data/provider/app.dart';
|
||||
import '../../locator.dart';
|
||||
import '../../view/widget/rebuild.dart';
|
||||
import 'platform.dart';
|
||||
|
||||
final _app = locator<AppProvider>();
|
||||
|
||||
Future<bool> shareFiles(BuildContext context, List<String> filePaths) async {
|
||||
for (final filePath in filePaths) {
|
||||
if (!await File(filePath).exists()) {
|
||||
@@ -18,8 +26,10 @@ Future<bool> shareFiles(BuildContext context, List<String> filePaths) async {
|
||||
} else {
|
||||
text = '${filePaths.length} ${S.of(context)!.files}';
|
||||
}
|
||||
final xfiles = filePaths.map((e) => XFile(e)).toList();
|
||||
await Share.shareXFiles(xfiles, text: 'ServerBox -> $text');
|
||||
_app.setMoveBg(false);
|
||||
// ignore: deprecated_member_use
|
||||
await Share.shareFiles(filePaths, text: 'ServerBox -> $text');
|
||||
_app.setMoveBg(true);
|
||||
return filePaths.isNotEmpty;
|
||||
}
|
||||
|
||||
@@ -28,6 +38,32 @@ void copy(String text) {
|
||||
}
|
||||
|
||||
Future<String?> pickOneFile() async {
|
||||
_app.setMoveBg(false);
|
||||
final result = await FilePicker.platform.pickFiles(type: FileType.any);
|
||||
_app.setMoveBg(true);
|
||||
return result?.files.single.path;
|
||||
}
|
||||
|
||||
Future<String?> getToken() async {
|
||||
if (isIOS) {
|
||||
final plainNotificationToken = PlainNotificationToken();
|
||||
plainNotificationToken.requestPermission();
|
||||
|
||||
// If you want to wait until Permission dialog close,
|
||||
// you need wait changing setting registered.
|
||||
await plainNotificationToken.onIosSettingsRegistered.first;
|
||||
return await plainNotificationToken.getToken();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? getFileName(String? path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return path.split('/').last;
|
||||
}
|
||||
|
||||
void rebuildAll(BuildContext context) {
|
||||
RebuildWidget.restartApp(context);
|
||||
}
|
||||
|
||||
7
lib/core/utils/navigator.dart
Normal file
7
lib/core/utils/navigator.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension ContextX on BuildContext {
|
||||
void pop<T>([T? result]) {
|
||||
Navigator.of(this).pop(T);
|
||||
}
|
||||
}
|
||||
49
lib/core/utils/platform.dart
Normal file
49
lib/core/utils/platform.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
enum PlatformType {
|
||||
android,
|
||||
ios,
|
||||
linux,
|
||||
macos,
|
||||
windows,
|
||||
web,
|
||||
unknown,
|
||||
}
|
||||
|
||||
final _p = () {
|
||||
if (kIsWeb) {
|
||||
return PlatformType.web;
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
return PlatformType.android;
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
return PlatformType.ios;
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
return PlatformType.linux;
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
return PlatformType.macos;
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
return PlatformType.windows;
|
||||
}
|
||||
return PlatformType.unknown;
|
||||
}();
|
||||
|
||||
PlatformType get platform => _p;
|
||||
|
||||
bool get isAndroid => _p == PlatformType.android;
|
||||
bool get isIOS => _p == PlatformType.ios;
|
||||
bool get isLinux => _p == PlatformType.linux;
|
||||
bool get isMacOS => _p == PlatformType.macos;
|
||||
bool get isWindows => _p == PlatformType.windows;
|
||||
bool get isWeb => _p == PlatformType.web;
|
||||
bool get isMobile => _p == PlatformType.ios || _p == PlatformType.android;
|
||||
bool get isDesktop =>
|
||||
_p == PlatformType.linux ||
|
||||
_p == PlatformType.macos ||
|
||||
_p == PlatformType.windows;
|
||||
@@ -9,7 +9,7 @@ import '../../locator.dart';
|
||||
|
||||
/// Must put this func out of any Class.
|
||||
///
|
||||
/// Because of this function is called by [compute] in [ServerProvider.genClient].
|
||||
/// Because of this function is called by [compute].
|
||||
///
|
||||
/// https://stackoverflow.com/questions/51998995/invalid-arguments-illegal-argument-in-isolate-message-object-is-a-closure
|
||||
List<SSHKeyPair> loadIndentity(String key) {
|
||||
@@ -24,13 +24,23 @@ String decyptPem(List<String> args) {
|
||||
return sshKey.first.toPem();
|
||||
}
|
||||
|
||||
Future<SSHClient> genClient(ServerPrivateInfo spi) async {
|
||||
enum GenSSHClientStatus {
|
||||
socket,
|
||||
key,
|
||||
pwd,
|
||||
}
|
||||
|
||||
Future<SSHClient> genClient(ServerPrivateInfo spi,
|
||||
{void Function(GenSSHClientStatus)? onStatus}) async {
|
||||
final onStatus_ = onStatus ?? (_) {};
|
||||
onStatus_(GenSSHClientStatus.socket);
|
||||
final socket = await SSHSocket.connect(
|
||||
spi.ip,
|
||||
spi.port,
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
if (spi.pubKeyId == null) {
|
||||
onStatus_(GenSSHClientStatus.pwd);
|
||||
return SSHClient(
|
||||
socket,
|
||||
username: spi.user,
|
||||
@@ -38,6 +48,7 @@ Future<SSHClient> genClient(ServerPrivateInfo spi) async {
|
||||
);
|
||||
}
|
||||
final key = locator<PrivateKeyStore>().get(spi.pubKeyId!);
|
||||
onStatus_(GenSSHClientStatus.key);
|
||||
return SSHClient(
|
||||
socket,
|
||||
username: spi.user,
|
||||
|
||||
@@ -3,11 +3,21 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/core/extension/stringx.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../data/model/server/snippet.dart';
|
||||
import '../../data/provider/snippet.dart';
|
||||
import '../../locator.dart';
|
||||
import '../../view/page/snippet/edit.dart';
|
||||
import '../../view/widget/card_dialog.dart';
|
||||
import '../../view/widget/picker.dart';
|
||||
import '../persistant_store.dart';
|
||||
import '../route.dart';
|
||||
import 'misc.dart';
|
||||
import 'platform.dart';
|
||||
import '../extension/stringx.dart';
|
||||
import '../extension/uint8list.dart';
|
||||
|
||||
bool isDarkMode(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark;
|
||||
@@ -62,7 +72,7 @@ Widget buildSwitch(BuildContext context, StoreProperty<bool> prop,
|
||||
}
|
||||
|
||||
void setTransparentNavigationBar(BuildContext context) {
|
||||
if (Platform.isAndroid) {
|
||||
if (isAndroid) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
@@ -101,3 +111,57 @@ String tabTitleName(BuildContext context, int i) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadFontFile(String? localPath) async {
|
||||
if (localPath == null) return;
|
||||
final name = getFileName(localPath);
|
||||
if (name == null) return;
|
||||
var fontLoader = FontLoader(name);
|
||||
fontLoader.addFont(File(localPath).readAsBytes().byteData);
|
||||
await fontLoader.load();
|
||||
}
|
||||
|
||||
void showSnippetDialog(
|
||||
BuildContext context, S s, Function(Snippet s) onSelected) {
|
||||
final provider = locator<SnippetProvider>();
|
||||
if (provider.snippets.isEmpty) {
|
||||
showRoundDialog(
|
||||
context,
|
||||
s.attention,
|
||||
Text(s.noSavedSnippet),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(s.ok),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
AppRoute(const SnippetEditPage(), 'edit snippet').go(context);
|
||||
},
|
||||
child: Text(s.addOne),
|
||||
)
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var snippet = provider.snippets.first;
|
||||
showRoundDialog(
|
||||
context,
|
||||
s.choose,
|
||||
buildPicker(
|
||||
provider.snippets.map((e) => Text(e.name)).toList(),
|
||||
(idx) => snippet = provider.snippets[idx],
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
onSelected(snippet);
|
||||
},
|
||||
child: Text(s.ok),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ class Backup {
|
||||
final List<ServerPrivateInfo> spis;
|
||||
final List<Snippet> snippets;
|
||||
final List<PrivateKeyInfo> keys;
|
||||
final int primaryColor;
|
||||
final int serverStatusUpdateInterval;
|
||||
final int launchPage;
|
||||
final Map<String, String> dockerHosts;
|
||||
|
||||
Backup(
|
||||
this.version,
|
||||
@@ -19,9 +17,7 @@ class Backup {
|
||||
this.spis,
|
||||
this.snippets,
|
||||
this.keys,
|
||||
this.primaryColor,
|
||||
this.serverStatusUpdateInterval,
|
||||
this.launchPage,
|
||||
this.dockerHosts,
|
||||
);
|
||||
|
||||
Backup.fromJson(Map<String, dynamic> json)
|
||||
@@ -35,9 +31,7 @@ class Backup {
|
||||
keys = (json['keys'] as List)
|
||||
.map((e) => PrivateKeyInfo.fromJson(e))
|
||||
.toList(),
|
||||
primaryColor = json['primaryColor'],
|
||||
serverStatusUpdateInterval = json['serverStatusUpdateInterval'],
|
||||
launchPage = json['launchPage'];
|
||||
dockerHosts = json['dockerHosts'] ?? {};
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'version': version,
|
||||
@@ -45,8 +39,5 @@ class Backup {
|
||||
'spis': spis,
|
||||
'snippets': snippets,
|
||||
'keys': keys,
|
||||
'primaryColor': primaryColor,
|
||||
'serverStatusUpdateInterval': serverStatusUpdateInterval,
|
||||
'launchPage': launchPage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,54 +1,126 @@
|
||||
///
|
||||
/// Code generated by jsonToDartModel https://ashamp.github.io/jsonToDartModel/
|
||||
///
|
||||
class AppUpdate {
|
||||
/*
|
||||
{
|
||||
"newest": 33,
|
||||
"android": "https://v2.custed.lolli.tech/res/tiku/apk/33.apk",
|
||||
"ios": "https://",
|
||||
"min": 30,
|
||||
"changelog": ""
|
||||
}
|
||||
"changelog": {
|
||||
"mac": "xxx",
|
||||
"ios": "xxx",
|
||||
"android": ""
|
||||
},
|
||||
"build": {
|
||||
"min": {
|
||||
"mac": 1,
|
||||
"ios": 1,
|
||||
"android": 1
|
||||
},
|
||||
"last": {
|
||||
"mac": 1,
|
||||
"ios": 1,
|
||||
"android": 1
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"mac": "https://apps.apple.com/app/id1586449703",
|
||||
"ios": "https://apps.apple.com/app/id1586449703",
|
||||
"android": "https://cdn3.cust.app/uploads/ServerBox_262_Arm64.apk"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
late int newest;
|
||||
late int iosbuild;
|
||||
late int androidbuild;
|
||||
late int macbuild;
|
||||
late String android;
|
||||
late String ios;
|
||||
late String mac;
|
||||
late int min;
|
||||
late String changelog;
|
||||
import 'dart:convert';
|
||||
|
||||
import '/core/utils/platform.dart';
|
||||
|
||||
class AppUpdate {
|
||||
AppUpdate({
|
||||
required this.newest,
|
||||
required this.android,
|
||||
required this.ios,
|
||||
required this.min,
|
||||
required this.changelog,
|
||||
required this.build,
|
||||
required this.url,
|
||||
});
|
||||
AppUpdate.fromJson(Map<String, dynamic> json) {
|
||||
newest = json["newest"]?.toInt();
|
||||
macbuild = json["macbuild"]?.toInt();
|
||||
iosbuild = json["iosbuild"]?.toInt();
|
||||
androidbuild = json["androidbuild"]?.toInt();
|
||||
android = json["android"].toString();
|
||||
ios = json["ios"].toString();
|
||||
min = json["min"].toInt();
|
||||
changelog = json["changelog"].toString();
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data["newest"] = newest;
|
||||
data["macbuild"] = macbuild;
|
||||
data["iosbuild"] = iosbuild;
|
||||
data["androidbuild"] = androidbuild;
|
||||
data["android"] = android;
|
||||
data["ios"] = ios;
|
||||
data["min"] = min;
|
||||
data["changelog"] = changelog;
|
||||
return data;
|
||||
|
||||
final AppUpdatePlatformSpecific<String> changelog;
|
||||
final Build build;
|
||||
final AppUpdatePlatformSpecific<String> url;
|
||||
|
||||
factory AppUpdate.fromRawJson(String str) =>
|
||||
AppUpdate.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory AppUpdate.fromJson(Map<String, dynamic> json) => AppUpdate(
|
||||
changelog: AppUpdatePlatformSpecific.fromJson(json["changelog"]),
|
||||
build: Build.fromJson(json["build"]),
|
||||
url: AppUpdatePlatformSpecific.fromJson(json["url"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"changelog": changelog.toJson(),
|
||||
"build": build.toJson(),
|
||||
"url": url.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class Build {
|
||||
Build({
|
||||
required this.min,
|
||||
required this.last,
|
||||
});
|
||||
|
||||
final AppUpdatePlatformSpecific<int> min;
|
||||
final AppUpdatePlatformSpecific<int> last;
|
||||
|
||||
factory Build.fromRawJson(String str) => Build.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory Build.fromJson(Map<String, dynamic> json) => Build(
|
||||
min: AppUpdatePlatformSpecific.fromJson(json["min"]),
|
||||
last: AppUpdatePlatformSpecific.fromJson(json["last"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"min": min.toJson(),
|
||||
"last": last.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class AppUpdatePlatformSpecific<T> {
|
||||
AppUpdatePlatformSpecific({
|
||||
required this.mac,
|
||||
required this.ios,
|
||||
required this.android,
|
||||
});
|
||||
|
||||
final T mac;
|
||||
final T ios;
|
||||
final T android;
|
||||
|
||||
factory AppUpdatePlatformSpecific.fromRawJson(String str) =>
|
||||
AppUpdatePlatformSpecific.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory AppUpdatePlatformSpecific.fromJson(Map<String, dynamic> json) =>
|
||||
AppUpdatePlatformSpecific(
|
||||
mac: json["mac"],
|
||||
ios: json["ios"],
|
||||
android: json["android"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"mac": mac,
|
||||
"ios": ios,
|
||||
"android": android,
|
||||
};
|
||||
|
||||
T? get current {
|
||||
switch (platform) {
|
||||
case PlatformType.macos:
|
||||
return mac;
|
||||
case PlatformType.ios:
|
||||
return ios;
|
||||
case PlatformType.android:
|
||||
return android;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,10 +114,8 @@ List<OneTimeCpuStatus> parseCPU(String raw) {
|
||||
|
||||
final cpuTempReg = RegExp(r'(x86_pkg_temp|cpu_thermal)');
|
||||
|
||||
String parseCPUTemp(List<String> segments) {
|
||||
String parseCPUTemp(String type, String value) {
|
||||
const noMatch = "/sys/class/thermal/thermal_zone*/type";
|
||||
final type = segments[0];
|
||||
final value = segments[1];
|
||||
// Not support to get CPU temperature
|
||||
if (value.contains(noMatch) ||
|
||||
type.contains(noMatch) ||
|
||||
|
||||
@@ -6,9 +6,9 @@ class Server {
|
||||
ServerPrivateInfo spi;
|
||||
ServerStatus status;
|
||||
SSHClient? client;
|
||||
ServerState cs;
|
||||
ServerState state;
|
||||
|
||||
Server(this.spi, this.status, this.client, this.cs);
|
||||
Server(this.spi, this.status, this.client, this.state);
|
||||
}
|
||||
|
||||
enum ServerState {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:xterm/ui.dart';
|
||||
import '../../res/terminal_color.dart';
|
||||
import '../../res/terminal.dart';
|
||||
|
||||
class TerminalUITheme {
|
||||
final Color cursor;
|
||||
|
||||
@@ -12,4 +12,4 @@ class VirtualKey {
|
||||
{this.key, this.toggleable = false, this.icon, this.func});
|
||||
}
|
||||
|
||||
enum VirtualKeyFunc { toggleIME, backspace, copy, paste }
|
||||
enum VirtualKeyFunc { toggleIME, backspace, copy, paste, snippet }
|
||||
|
||||
@@ -4,8 +4,15 @@ class AppProvider extends BusyProvider {
|
||||
int? _newestBuild;
|
||||
int? get newestBuild => _newestBuild;
|
||||
|
||||
bool _moveBg = true;
|
||||
bool get moveBg => _moveBg;
|
||||
|
||||
void setNewestBuild(int build) {
|
||||
_newestBuild = build;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setMoveBg(bool moveBg) {
|
||||
_moveBg = moveBg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:toolbox/data/model/server/server_status.dart';
|
||||
|
||||
import '../../core/extension/uint8list.dart';
|
||||
import '../../core/provider_base.dart';
|
||||
@@ -49,7 +50,7 @@ class ServerProvider extends BusyProvider {
|
||||
}
|
||||
await Future.wait(_servers.map((s) async {
|
||||
if (onlyFailed) {
|
||||
if (s.cs != ServerState.failed) return;
|
||||
if (s.state != ServerState.failed) return;
|
||||
_limiter.resetTryTimes(s.spi.id);
|
||||
}
|
||||
await _getData(s.spi);
|
||||
@@ -75,7 +76,7 @@ class ServerProvider extends BusyProvider {
|
||||
|
||||
void setDisconnected() {
|
||||
for (var i = 0; i < _servers.length; i++) {
|
||||
_servers[i].cs = ServerState.disconnected;
|
||||
_servers[i].state = ServerState.disconnected;
|
||||
}
|
||||
_limiter.clear();
|
||||
notifyListeners();
|
||||
@@ -87,13 +88,11 @@ class ServerProvider extends BusyProvider {
|
||||
_servers[i].client?.close();
|
||||
_servers[i].client = null;
|
||||
}
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
final idx = getServerIdx(spi.id);
|
||||
_servers[idx].client?.close();
|
||||
_servers[idx].client = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addServer(ServerPrivateInfo spi) {
|
||||
@@ -137,14 +136,14 @@ class ServerProvider extends BusyProvider {
|
||||
Future<void> _getData(ServerPrivateInfo spi) async {
|
||||
final sid = spi.id;
|
||||
final s = getServer(sid);
|
||||
final state = s.cs;
|
||||
final state = s.state;
|
||||
if (state.shouldConnect) {
|
||||
if (!_limiter.shouldTry(sid)) {
|
||||
s.cs = ServerState.failed;
|
||||
s.state = ServerState.failed;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
s.cs = ServerState.connecting;
|
||||
s.state = ServerState.connecting;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
@@ -156,10 +155,8 @@ class ServerProvider extends BusyProvider {
|
||||
_logger.info('Connected to [$sid] in $spentTime ms.');
|
||||
|
||||
// after connected
|
||||
s.cs = ServerState.connected;
|
||||
final writeResult = await s.client!
|
||||
.run("echo '$shellCmd' > $shellPath && chmod +x $shellPath")
|
||||
.string;
|
||||
s.state = ServerState.connected;
|
||||
final writeResult = await s.client!.run(installShellCmd).string;
|
||||
|
||||
// if write failed
|
||||
if (writeResult.isNotEmpty) {
|
||||
@@ -167,8 +164,8 @@ class ServerProvider extends BusyProvider {
|
||||
}
|
||||
_limiter.resetTryTimes(sid);
|
||||
} catch (e) {
|
||||
s.cs = ServerState.failed;
|
||||
s.status.failedInfo = '$e';
|
||||
s.state = ServerState.failed;
|
||||
s.status.failedInfo = e.toString();
|
||||
_logger.warning(e);
|
||||
} finally {
|
||||
notifyListeners();
|
||||
@@ -180,7 +177,7 @@ class ServerProvider extends BusyProvider {
|
||||
final raw = await s.client!.run("sh $shellPath").string;
|
||||
final segments = raw.split(seperator).map((e) => e.trim()).toList();
|
||||
if (raw.isEmpty || segments.length == 1) {
|
||||
s.cs = ServerState.failed;
|
||||
s.state = ServerState.failed;
|
||||
if (s.status.failedInfo == null || s.status.failedInfo!.isEmpty) {
|
||||
s.status.failedInfo = 'Seperate segments failed, raw:\n$raw';
|
||||
}
|
||||
@@ -188,18 +185,14 @@ class ServerProvider extends BusyProvider {
|
||||
return;
|
||||
}
|
||||
// remove first empty segment
|
||||
// for `export xxx` in shell script
|
||||
segments.removeAt(0);
|
||||
|
||||
try {
|
||||
_getCPU(sid, segments[2], segments[7], segments[8]);
|
||||
_getMem(sid, segments[6]);
|
||||
_getSysVer(sid, segments[1]);
|
||||
_getUpTime(sid, segments[3]);
|
||||
_getDisk(sid, segments[5]);
|
||||
_getTcp(sid, segments[4]);
|
||||
_getNetSpeed(sid, segments[0]);
|
||||
final req = ServerStatusUpdateReq(s.status, segments);
|
||||
s.status = await compute(_getStatus, req);
|
||||
} catch (e) {
|
||||
s.cs = ServerState.failed;
|
||||
s.state = ServerState.failed;
|
||||
s.status.failedInfo = e.toString();
|
||||
_logger.warning(e);
|
||||
rethrow;
|
||||
@@ -208,50 +201,6 @@ class ServerProvider extends BusyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getNetSpeed(String id, String raw) async {
|
||||
final net = await compute(parseNetSpeed, raw);
|
||||
getServer(id).status.netSpeed.update(net);
|
||||
}
|
||||
|
||||
void _getSysVer(String id, String raw) {
|
||||
final s = raw.split('=');
|
||||
if (s.length == 2) {
|
||||
final ver = s[1].replaceAll('"', '').replaceFirst('\n', '');
|
||||
getServer(id).status.sysVer = ver;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getCPU(
|
||||
String id, String raw, String tempType, String tempValue) async {
|
||||
final cpus = await compute(parseCPU, raw);
|
||||
final temp = await compute(parseCPUTemp, [tempType, tempValue]);
|
||||
|
||||
if (cpus.isNotEmpty) {
|
||||
getServer(id).status.cpu.update(cpus, temp);
|
||||
}
|
||||
}
|
||||
|
||||
void _getUpTime(String id, String raw) {
|
||||
getServer(id).status.uptime = raw.split('up ')[1].split(', ')[0];
|
||||
}
|
||||
|
||||
Future<void> _getTcp(String id, String raw) async {
|
||||
final status = await compute(parseTcp, raw);
|
||||
if (status != null) {
|
||||
getServer(id).status.tcp = status;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getDisk(String id, String raw) async {
|
||||
getServer(id).status.disk = await compute(parseDisk, raw);
|
||||
}
|
||||
|
||||
Future<void> _getMem(String id, String raw) async {
|
||||
final s = getServer(id);
|
||||
s.status.mem = await compute(parseMem, raw);
|
||||
s.status.swap = await compute(parseSwap, raw);
|
||||
}
|
||||
|
||||
Future<String?> runSnippet(String id, Snippet snippet) async {
|
||||
final client = getServer(id).client;
|
||||
if (client == null) {
|
||||
@@ -285,3 +234,55 @@ class _TryLimiter {
|
||||
_triedTimes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class ServerStatusUpdateReq {
|
||||
final ServerStatus ss;
|
||||
final List<String> segments;
|
||||
|
||||
const ServerStatusUpdateReq(this.ss, this.segments);
|
||||
}
|
||||
|
||||
Future<ServerStatus> _getStatus(ServerStatusUpdateReq req) async {
|
||||
final net = parseNetSpeed(req.segments[0]);
|
||||
req.ss.netSpeed.update(net);
|
||||
final sys = parseSysVer(req.segments[1]);
|
||||
if (sys != null) {
|
||||
req.ss.sysVer = sys;
|
||||
}
|
||||
final cpus = parseCPU(req.segments[2]);
|
||||
final cpuTemp = parseCPUTemp(req.segments[7], req.segments[8]);
|
||||
req.ss.cpu.update(cpus, cpuTemp);
|
||||
final tcp = parseTcp(req.segments[4]);
|
||||
if (tcp != null) {
|
||||
req.ss.tcp = tcp;
|
||||
}
|
||||
req.ss.disk = parseDisk(req.segments[5]);
|
||||
req.ss.mem = parseMem(req.segments[6]);
|
||||
final uptime = parseUpTime(req.segments[3]);
|
||||
if (uptime != null) {
|
||||
req.ss.uptime = uptime;
|
||||
}
|
||||
req.ss.swap = parseSwap(req.segments[6]);
|
||||
return req.ss;
|
||||
}
|
||||
|
||||
// raw:
|
||||
// 19:39:15 up 61 days, 18:16, 1 user, load average: 0.00, 0.00, 0.00
|
||||
String? parseUpTime(String raw) {
|
||||
final splitedUp = raw.split('up ');
|
||||
if (splitedUp.length == 2) {
|
||||
final splitedComma = splitedUp[1].split(', ');
|
||||
if (splitedComma.length >= 2) {
|
||||
return splitedComma[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? parseSysVer(String raw) {
|
||||
final s = raw.split('=');
|
||||
if (s.length == 2) {
|
||||
return s[1].replaceAll('"', '').replaceFirst('\n', '');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:toolbox/core/provider_base.dart';
|
||||
import '../model/sftp/download_item.dart';
|
||||
import '../model/sftp/download_status.dart';
|
||||
|
||||
class SftpDownloadProvider extends ProviderBase {
|
||||
class SftpProvider extends ProviderBase {
|
||||
final List<SftpDownloadStatus> _status = [];
|
||||
List<SftpDownloadStatus> get status => _status;
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
class BuildData {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 227;
|
||||
static const String engine =
|
||||
"Flutter 3.7.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 9944297138 (2 weeks ago) • 2023-02-08 15:46:04 -0800\nEngine • revision 248290d6d5\nTools • Dart 2.19.2 • DevTools 2.20.1\n";
|
||||
static const String buildAt = "2023-02-25 18:23:00.718232";
|
||||
static const int modifications = 13;
|
||||
static const int build = 269;
|
||||
static const String engine = "3.7.11";
|
||||
static const String buildAt = "2023-04-19 20:53:37.738261";
|
||||
static const int modifications = 15;
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
final appIcon = Image.asset('assets/app_icon.png');
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// RegExp for number
|
||||
final numReg = RegExp(r'\s{1,}');
|
||||
|
||||
/// Private Key max allowed size is 20kb
|
||||
@@ -5,3 +8,7 @@ const privateKeyMaxSize = 20 * 1024;
|
||||
|
||||
/// Max debug log lines
|
||||
const maxDebugLogLines = 100;
|
||||
|
||||
/// Method Channels
|
||||
const pkgName = 'tech.lolli.toolbox';
|
||||
const bgRunChannel = MethodChannel('$pkgName/app_retain');
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const roundRectCardPadding = EdgeInsets.symmetric(horizontal: 17, vertical: 13);
|
||||
@@ -1,10 +1,25 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:toolbox/core/utils/platform.dart';
|
||||
|
||||
Future<Directory> get docDir async => await getApplicationDocumentsDirectory();
|
||||
Future<Directory> get docDir async {
|
||||
if (isAndroid) {
|
||||
final dir = await getExternalStorageDirectory();
|
||||
if (dir != null) {
|
||||
return dir;
|
||||
}
|
||||
// fallthrough to getApplicationDocumentsDirectory
|
||||
}
|
||||
return await getApplicationDocumentsDirectory();
|
||||
}
|
||||
|
||||
Future<Directory> get sftpDownloadDir async {
|
||||
Future<Directory> get sftpDir async {
|
||||
final dir = Directory('${(await docDir).path}/sftp');
|
||||
return dir.create(recursive: true);
|
||||
}
|
||||
|
||||
Future<Directory> get fontDir async {
|
||||
final dir = Directory('${(await docDir).path}/font');
|
||||
return dir.create(recursive: true);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
const seperator = 'A====A';
|
||||
const shellCmd = "export LANG=en_US.utf-8 \necho '$seperator' \n"
|
||||
"cat /proc/net/dev && date +%s \necho $seperator \n"
|
||||
"cat /etc/os-release | grep PRETTY_NAME \necho $seperator \n"
|
||||
"cat /proc/stat | grep cpu \necho $seperator \n"
|
||||
"uptime \necho $seperator \n"
|
||||
"cat /proc/net/snmp \necho $seperator \n"
|
||||
"df -h \necho $seperator \n"
|
||||
"cat /proc/meminfo \necho $seperator \n"
|
||||
"cat /sys/class/thermal/thermal_zone*/type \necho $seperator \n"
|
||||
"cat /sys/class/thermal/thermal_zone*/temp";
|
||||
const shellPath = '.serverbox.sh';
|
||||
import 'build_data.dart';
|
||||
|
||||
const seperator = 'SrvBox';
|
||||
const serverBoxDir = r'$HOME/.config/server_box';
|
||||
const shellPath = '$serverBoxDir/mobile_app.sh';
|
||||
const installShellCmd =
|
||||
"mkdir -p $serverBoxDir && echo '$shellCmd' > $shellPath && chmod +x $shellPath";
|
||||
|
||||
const shellCmd = """
|
||||
# Script for app `${BuildData.name}`
|
||||
# Delete this file while app is running will cause app crash
|
||||
|
||||
export LANG=en_US.utf-8
|
||||
echo $seperator
|
||||
cat /proc/net/dev && date +%s
|
||||
echo $seperator
|
||||
cat /etc/os-release | grep PRETTY_NAME
|
||||
echo $seperator
|
||||
cat /proc/stat | grep cpu
|
||||
echo $seperator
|
||||
uptime
|
||||
echo $seperator
|
||||
cat /proc/net/snmp
|
||||
echo $seperator
|
||||
df -h
|
||||
echo $seperator
|
||||
cat /proc/meminfo
|
||||
echo $seperator
|
||||
cat /sys/class/thermal/thermal_zone*/type
|
||||
echo $seperator
|
||||
cat /sys/class/thermal/thermal_zone*/temp
|
||||
""";
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
const height13 = SizedBox(height: 13);
|
||||
const width13 = SizedBox(width: 13);
|
||||
const width7 = SizedBox(width: 7);
|
||||
@@ -5,14 +5,14 @@ import '../model/server/net_speed.dart';
|
||||
import '../model/server/server_status.dart';
|
||||
import '../model/server/tcp_status.dart';
|
||||
|
||||
get _initMemory => Memory(
|
||||
Memory get _initMemory => Memory(
|
||||
total: 1,
|
||||
used: 0,
|
||||
free: 1,
|
||||
cache: 0,
|
||||
avail: 1,
|
||||
);
|
||||
get _initOneTimeCpuStatus => OneTimeCpuStatus(
|
||||
OneTimeCpuStatus get _initOneTimeCpuStatus => OneTimeCpuStatus(
|
||||
'cpu',
|
||||
0,
|
||||
0,
|
||||
@@ -22,28 +22,28 @@ get _initOneTimeCpuStatus => OneTimeCpuStatus(
|
||||
0,
|
||||
0,
|
||||
);
|
||||
get initCpuStatus => CpuStatus(
|
||||
CpuStatus get initCpuStatus => CpuStatus(
|
||||
[_initOneTimeCpuStatus],
|
||||
[_initOneTimeCpuStatus],
|
||||
'',
|
||||
);
|
||||
get _initNetSpeedPart => NetSpeedPart(
|
||||
NetSpeedPart get _initNetSpeedPart => NetSpeedPart(
|
||||
'',
|
||||
BigInt.zero,
|
||||
BigInt.zero,
|
||||
BigInt.zero,
|
||||
);
|
||||
get initNetSpeed => NetSpeed(
|
||||
NetSpeed get initNetSpeed => NetSpeed(
|
||||
[_initNetSpeedPart],
|
||||
[_initNetSpeedPart],
|
||||
);
|
||||
get _initSwap => Swap(
|
||||
Swap get _initSwap => Swap(
|
||||
total: 1,
|
||||
used: 0,
|
||||
free: 1,
|
||||
cached: 0,
|
||||
);
|
||||
get initStatus => ServerStatus(
|
||||
ServerStatus get initStatus => ServerStatus(
|
||||
cpu: initCpuStatus,
|
||||
mem: _initMemory,
|
||||
sysVer: 'Loading...',
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/data/model/ssh/terminal_color.dart';
|
||||
|
||||
const termDarkTheme = TerminalUITheme(
|
||||
cursor: Color(0XAAAEAFAD),
|
||||
selection: Color(0XAAAEAFAD),
|
||||
foreground: Color(0XFFCCCCCC),
|
||||
background: Colors.black,
|
||||
searchHitBackground: Color(0XFFFFFF2B),
|
||||
searchHitBackgroundCurrent: Color(0XFF31FF26),
|
||||
searchHitForeground: Color(0XFF000000),
|
||||
);
|
||||
|
||||
const termLightTheme = TerminalUITheme(
|
||||
cursor: Color(0XFFAEAFAD),
|
||||
selection: Color.fromARGB(102, 174, 175, 173),
|
||||
foreground: Color(0XFF000000),
|
||||
background: Color(0XFFFFFFFF),
|
||||
searchHitBackground: Color(0XFFFFFF2B),
|
||||
searchHitBackgroundCurrent: Color(0XFF31FF26),
|
||||
searchHitForeground: Color(0XFF000000),
|
||||
);
|
||||
|
||||
class VGATerminalColor extends TerminalColors {
|
||||
VGATerminalColor()
|
||||
: super(
|
||||
@@ -1,23 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../model/ssh/terminal_color.dart';
|
||||
|
||||
const termDarkTheme = TerminalUITheme(
|
||||
cursor: Color(0XAAAEAFAD),
|
||||
selection: Color(0XAAAEAFAD),
|
||||
foreground: Color(0XFFCCCCCC),
|
||||
background: Colors.black,
|
||||
searchHitBackground: Color(0XFFFFFF2B),
|
||||
searchHitBackgroundCurrent: Color(0XFF31FF26),
|
||||
searchHitForeground: Color(0XFF000000),
|
||||
);
|
||||
|
||||
const termLightTheme = TerminalUITheme(
|
||||
cursor: Color(0XFFAEAFAD),
|
||||
selection: Color.fromARGB(102, 174, 175, 173),
|
||||
foreground: Color(0XFF000000),
|
||||
background: Color(0XFFFFFFFF),
|
||||
searchHitBackground: Color(0XFFFFFF2B),
|
||||
searchHitBackgroundCurrent: Color(0XFF31FF26),
|
||||
searchHitForeground: Color(0XFF000000),
|
||||
);
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Font style
|
||||
|
||||
const textSize18 = TextStyle(fontSize: 18);
|
||||
const textSize11 = TextStyle(fontSize: 11);
|
||||
const textSize13 = TextStyle(fontSize: 13);
|
||||
@@ -7,3 +9,17 @@ const textSize13Grey = TextStyle(color: Colors.grey, fontSize: 13);
|
||||
const textSize27 = TextStyle(fontSize: 27);
|
||||
|
||||
const grey = TextStyle(color: Colors.grey);
|
||||
|
||||
/// Icon
|
||||
|
||||
final appIcon = Image.asset('assets/app_icon.png');
|
||||
|
||||
/// Padding
|
||||
|
||||
const roundRectCardPadding = EdgeInsets.symmetric(horizontal: 17, vertical: 13);
|
||||
|
||||
/// SizedBox
|
||||
|
||||
const height13 = SizedBox(height: 13);
|
||||
const width13 = SizedBox(width: 13);
|
||||
const width7 = SizedBox(width: 7);
|
||||
@@ -1,5 +1,5 @@
|
||||
const backendUrl = 'https://res.lolli.tech';
|
||||
const baseUrl = '$backendUrl/toolbox';
|
||||
const baseUrl = '$backendUrl/serverbox';
|
||||
const joinQQGroupUrl = 'https://jq.qq.com/?_wv=1027&k=G0hUmPAq';
|
||||
const myGithub = 'https://github.com/lollipopkit';
|
||||
const issueUrl = '$myGithub/flutter_server_box/issues';
|
||||
@@ -11,4 +11,5 @@ const thanksMap = {
|
||||
'Tao173': 'https://github.com/Tao173',
|
||||
'QingAnLe': 'https://github.com/QingAnLe',
|
||||
'wxdjs': 'https://github.com/wxdjs',
|
||||
'Aeorq': 'https://github.com/Aeorq',
|
||||
};
|
||||
|
||||
@@ -3,22 +3,24 @@ import 'package:xterm/core.dart';
|
||||
|
||||
import '../model/ssh/virtual_key.dart';
|
||||
|
||||
var virtualKeys = [
|
||||
final virtualKeys = [
|
||||
VirtualKey('Esc', key: TerminalKey.escape),
|
||||
VirtualKey('Alt', key: TerminalKey.alt, toggleable: true),
|
||||
VirtualKey('Home', key: TerminalKey.home),
|
||||
VirtualKey('Up', key: TerminalKey.arrowUp, icon: Icons.arrow_upward),
|
||||
VirtualKey('End', key: TerminalKey.end),
|
||||
// VirtualKey(
|
||||
// 'Del',
|
||||
// key: TerminalKey.delete,
|
||||
// ),
|
||||
VirtualKey('Paste', func: VirtualKeyFunc.paste, icon: Icons.paste),
|
||||
VirtualKey(
|
||||
'Del',
|
||||
key: TerminalKey.delete,
|
||||
icon: Icons.backspace,
|
||||
),
|
||||
VirtualKey('Snippet', func: VirtualKeyFunc.snippet, icon: Icons.code),
|
||||
VirtualKey('Tab', key: TerminalKey.tab),
|
||||
VirtualKey('Ctrl', key: TerminalKey.control, toggleable: true),
|
||||
VirtualKey('Left', key: TerminalKey.arrowLeft, icon: Icons.arrow_back),
|
||||
VirtualKey('Down', key: TerminalKey.arrowDown, icon: Icons.arrow_downward),
|
||||
VirtualKey('Right', key: TerminalKey.arrowRight, icon: Icons.arrow_forward),
|
||||
VirtualKey('Paste', func: VirtualKeyFunc.paste, icon: Icons.paste),
|
||||
VirtualKey(
|
||||
'IME',
|
||||
func: VirtualKeyFunc.toggleIME,
|
||||
|
||||
@@ -8,4 +8,8 @@ class DockerStore extends PersistentStore {
|
||||
void setDockerHost(String id, String host) {
|
||||
box.put(id, host);
|
||||
}
|
||||
|
||||
Map<String, String> fetch() {
|
||||
return box.toMap().cast<String, String>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,14 @@ class SettingStore extends PersistentStore {
|
||||
|
||||
/// Max retry count when connect to server
|
||||
StoreProperty<int> get maxRetryCount =>
|
||||
property('maxRetryCount', defaultValue: 7);
|
||||
property('maxRetryCount', defaultValue: 2);
|
||||
|
||||
/// Night mode: 0 -> auto, 1 -> light, 2 -> dark
|
||||
StoreProperty<int> get nightMode =>
|
||||
property('nightMode', defaultValue: ThemeMode.system.index);
|
||||
StoreProperty<int> get themeMode => property('themeMode', defaultValue: 0);
|
||||
|
||||
/// Font file path
|
||||
StoreProperty<String> get fontPath => property('fontPath');
|
||||
|
||||
/// Backgroud running (Android)
|
||||
StoreProperty<bool> get bgRun => property('bgRun', defaultValue: true);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"about": "About",
|
||||
"aboutThanks": "\n\nThanks to the following people who participated in the test.",
|
||||
"aboutThanks": "\nThanks to the following people who participated in the test.",
|
||||
"addAServer": "add a server",
|
||||
"addOne": "Add one",
|
||||
"addPrivateKey": "Add private key",
|
||||
@@ -10,11 +10,14 @@
|
||||
"auto": "Auto",
|
||||
"backDir": "Back",
|
||||
"backup": "Backup",
|
||||
"backupTip": "The exported data is simply encrypted. \nPlease keep it safe.\nRestoring will not overwrite existing data (except setting).",
|
||||
"backupAndRestore": "Backup and Restore",
|
||||
"backupTip": "The exported data is simply encrypted. \nPlease keep it safe.",
|
||||
"backupVersionNotMatch": "Backup version is not match.",
|
||||
"bgRun": "Run in backgroud",
|
||||
"cancel": "Cancel",
|
||||
"choose": "Choose",
|
||||
"chooseDestination": "Choose destination",
|
||||
"chooseFontFile": "Choose a font file",
|
||||
"choosePrivateKey": "Choose private key",
|
||||
"clear": "Clear",
|
||||
"clickSee": "Click here",
|
||||
@@ -51,6 +54,7 @@
|
||||
"experimentalFeature": "Experimental feature",
|
||||
"export": "Export",
|
||||
"extraArgs": "Extra args",
|
||||
"failed": "Failed",
|
||||
"feedback": "Feedback",
|
||||
"feedbackOnGithub": "If you have any questions, please feedback on Github.",
|
||||
"fieldMustNotEmpty": "These fields must not be empty.",
|
||||
@@ -58,13 +62,14 @@
|
||||
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
|
||||
"files": "Files",
|
||||
"foundNUpdate": "Found {count} update",
|
||||
"getPushTokenFailed": "Can't fetch push token",
|
||||
"gettingToken": "Getting token...",
|
||||
"go": "Go",
|
||||
"goto": "Go to",
|
||||
"host": "Host",
|
||||
"httpFailedWithCode": "request failed, status code: {code}",
|
||||
"imagesList": "Images list",
|
||||
"import": "Import",
|
||||
"importAndExport": "Import and Export",
|
||||
"inputDomainHere": "Input Domain here",
|
||||
"install": "install",
|
||||
"installDockerWithUrl": "Please https://docs.docker.com/engine/install docker first.",
|
||||
@@ -80,13 +85,14 @@
|
||||
"light": "Light",
|
||||
"loadingFiles": "Loading files...",
|
||||
"loss": "loss",
|
||||
"madeWithLove": "\nMade with ❤️ by {myGithub}",
|
||||
"madeWithLove": "Made with ❤️ by {myGithub}",
|
||||
"max": "max",
|
||||
"maxRetryCount": "Number of server reconnection",
|
||||
"maxRetryCountEqual0": "Will retry again and again.",
|
||||
"min": "min",
|
||||
"ms": "ms",
|
||||
"name": "Name",
|
||||
"needRestart": "Need to restart app",
|
||||
"newContainer": "New container",
|
||||
"noClient": "No client",
|
||||
"noInterface": "No interface",
|
||||
@@ -95,9 +101,13 @@
|
||||
"noSavedSnippet": "No saved snippets.",
|
||||
"noServerAvailable": "No server available.",
|
||||
"noUpdateAvailable": "No update available",
|
||||
"notSelected": "Not selected",
|
||||
"nullToken": "Null token",
|
||||
"ok": "OK",
|
||||
"onServerDetailPage": "On server detail page",
|
||||
"onlyIOS": "Only valid on iOS",
|
||||
"open": "Open",
|
||||
"paste": "Paste",
|
||||
"path": "Path",
|
||||
"pickFile": "Pick file",
|
||||
"ping": "Ping",
|
||||
@@ -111,9 +121,11 @@
|
||||
"port": "Port",
|
||||
"preview": "Preview",
|
||||
"privateKey": "Private Key",
|
||||
"pushToken": "Push token",
|
||||
"pwd": "Password",
|
||||
"rename": "Rename",
|
||||
"reportBugsOnGithubIssue": "Please report bugs on {url}",
|
||||
"restart": "Restart",
|
||||
"restore": "Restore",
|
||||
"restoreSuccess": "Restore success. Restart app to apply.",
|
||||
"restoreSureWithDate": "Are you sure to restore from {date} ?",
|
||||
@@ -138,7 +150,9 @@
|
||||
"sshTip": "This function is now in the experimental stage.\n\nPlease report bugs on {url} or join our development.",
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"success": "Success",
|
||||
"sureDelete": "Are you sure to delete [{name}]?",
|
||||
"sureDirEmpty": "Make sure dir is empty.",
|
||||
"sureNoPwd": "Are you sure to use no password?",
|
||||
"sureToDeleteServer": "Are you sure to delete server [{server}]?",
|
||||
"termTheme": "Terminal theme",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"about": "关于",
|
||||
"aboutThanks": "\n\n感谢以下参与软件测试的各位。",
|
||||
"aboutThanks": "\n感谢以下参与软件测试的各位。",
|
||||
"addAServer": "添加服务器",
|
||||
"addOne": "前去新增",
|
||||
"addPrivateKey": "添加一个私钥",
|
||||
@@ -10,11 +10,14 @@
|
||||
"auto": "自动",
|
||||
"backDir": "返回上一级",
|
||||
"backup": "备份",
|
||||
"backupTip": "导出的数据仅进行了简单加密,请妥善保管。\n除了设置项,恢复的数据不会覆盖现有数据。",
|
||||
"backupAndRestore": "备份和恢复",
|
||||
"backupTip": "导出的数据仅进行了简单加密,请妥善保管。",
|
||||
"backupVersionNotMatch": "备份版本不匹配,无法恢复",
|
||||
"bgRun": "后台运行",
|
||||
"cancel": "取消",
|
||||
"choose": "选择",
|
||||
"chooseDestination": "选择目标",
|
||||
"chooseFontFile": "选择字体文件",
|
||||
"choosePrivateKey": "选择私钥",
|
||||
"clear": "清除",
|
||||
"clickSee": "点击查看",
|
||||
@@ -51,6 +54,7 @@
|
||||
"experimentalFeature": "实验性功能",
|
||||
"export": "导出",
|
||||
"extraArgs": "额外参数",
|
||||
"failed": "失败",
|
||||
"feedback": "反馈",
|
||||
"feedbackOnGithub": "如果你有任何问题,请在GitHub反馈",
|
||||
"fieldMustNotEmpty": "这些输入框不能为空。",
|
||||
@@ -58,13 +62,14 @@
|
||||
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
|
||||
"files": "文件",
|
||||
"foundNUpdate": "找到 {count} 个更新",
|
||||
"getPushTokenFailed": "未能获取到推送token",
|
||||
"gettingToken": "正在获取Token...",
|
||||
"go": "开始",
|
||||
"goto": "前往",
|
||||
"host": "主机",
|
||||
"httpFailedWithCode": "请求失败, 状态码: {code}",
|
||||
"imagesList": "镜像列表",
|
||||
"import": "导入",
|
||||
"importAndExport": "导入或导出",
|
||||
"inputDomainHere": "在这里输入域名",
|
||||
"install": "安装",
|
||||
"installDockerWithUrl": "请先 https://docs.docker.com/engine/install docker",
|
||||
@@ -80,13 +85,14 @@
|
||||
"light": "亮",
|
||||
"loadingFiles": "正在加载目录。。。",
|
||||
"loss": "丢包率",
|
||||
"madeWithLove": "\n用❤️制作 by {myGithub}",
|
||||
"madeWithLove": "用❤️制作 by {myGithub}",
|
||||
"max": "最大",
|
||||
"maxRetryCount": "服务器尝试重连次数",
|
||||
"maxRetryCountEqual0": "会无限重试",
|
||||
"min": "最小",
|
||||
"ms": "毫秒",
|
||||
"name": "名称",
|
||||
"needRestart": "需要重启 App",
|
||||
"newContainer": "新建容器",
|
||||
"noClient": "没有SSH连接",
|
||||
"noInterface": "没有可用的接口",
|
||||
@@ -95,9 +101,13 @@
|
||||
"noSavedSnippet": "没有已保存的代码片段。",
|
||||
"noServerAvailable": "没有可用的服务器。",
|
||||
"noUpdateAvailable": "没有可用更新",
|
||||
"notSelected": "未选择",
|
||||
"nullToken": "无Token",
|
||||
"ok": "好",
|
||||
"onServerDetailPage": "在服务器详情页",
|
||||
"onlyIOS": "仅在iOS上有效",
|
||||
"open": "打开",
|
||||
"paste": "粘贴",
|
||||
"path": "路径",
|
||||
"pickFile": "选择文件",
|
||||
"ping": "Ping",
|
||||
@@ -111,9 +121,11 @@
|
||||
"port": "端口",
|
||||
"preview": "预览",
|
||||
"privateKey": "私钥",
|
||||
"pushToken": "消息推送 Token",
|
||||
"pwd": "密码",
|
||||
"rename": "重命名",
|
||||
"reportBugsOnGithubIssue": "请到 {url} 提交问题",
|
||||
"restart": "重启",
|
||||
"restore": "恢复",
|
||||
"restoreSuccess": "恢复成功,需要重启App来应用更改",
|
||||
"restoreSureWithDate": "确定恢复 {date} 的备份吗?",
|
||||
@@ -138,7 +150,9 @@
|
||||
"sshTip": "该功能目前处于测试阶段。\n\n请在 {url} 反馈问题,或者加入我们开发。",
|
||||
"start": "开始",
|
||||
"stop": "停止",
|
||||
"success": "成功",
|
||||
"sureDelete": "确定删除[{name}]?",
|
||||
"sureDirEmpty": "请确保文件夹为空",
|
||||
"sureNoPwd": "确认使用无密码?",
|
||||
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
|
||||
"termTheme": "终端主题",
|
||||
|
||||
@@ -31,7 +31,7 @@ void setupLocatorForProviders() {
|
||||
locator.registerSingleton(VirtualKeyboard());
|
||||
locator.registerSingleton(SnippetProvider());
|
||||
locator.registerSingleton(PrivateKeyProvider());
|
||||
locator.registerSingleton(SftpDownloadProvider());
|
||||
locator.registerSingleton(SftpProvider());
|
||||
}
|
||||
|
||||
Future<void> setupLocatorForStores() async {
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'app.dart';
|
||||
import 'core/analysis.dart';
|
||||
import 'core/utils/ui.dart';
|
||||
import 'data/model/server/private_key_info.dart';
|
||||
import 'data/model/server/server_private_info.dart';
|
||||
import 'data/model/server/snippet.dart';
|
||||
@@ -19,7 +20,9 @@ import 'data/provider/server.dart';
|
||||
import 'data/provider/sftp_download.dart';
|
||||
import 'data/provider/snippet.dart';
|
||||
import 'data/provider/virtual_keyboard.dart';
|
||||
import 'data/store/setting.dart';
|
||||
import 'locator.dart';
|
||||
import 'view/widget/rebuild.dart';
|
||||
|
||||
late final DebugProvider _debug;
|
||||
|
||||
@@ -31,6 +34,9 @@ Future<void> initApp() async {
|
||||
locator<SnippetProvider>().loadData();
|
||||
locator<PrivateKeyProvider>().loadData();
|
||||
|
||||
final settings = locator<SettingStore>();
|
||||
await loadFontFile(settings.fontPath.fetch());
|
||||
|
||||
///设置Logger
|
||||
Logger.root.level = Level.ALL; // defaults to Level.INFO
|
||||
Logger.root.onRecord.listen((record) {
|
||||
@@ -86,10 +92,11 @@ Future<void> main() async {
|
||||
ChangeNotifierProvider(create: (_) => locator<SnippetProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<VirtualKeyboard>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<PrivateKeyProvider>()),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => locator<SftpDownloadProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<SftpProvider>()),
|
||||
],
|
||||
child: MyApp(),
|
||||
child: RebuildWidget(
|
||||
child: MyApp(),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/data/res/color.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
import 'package:toolbox/data/res/path.dart';
|
||||
|
||||
import '../../core/extension/colorx.dart';
|
||||
import '../../core/utils/misc.dart';
|
||||
import '../../core/utils/ui.dart';
|
||||
import '../../data/model/app/backup.dart';
|
||||
import '../../data/res/font_style.dart';
|
||||
import '../../data/res/color.dart';
|
||||
import '../../data/res/ui.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';
|
||||
|
||||
@@ -22,10 +25,10 @@ const backupFormatVersion = 1;
|
||||
class BackupPage extends StatelessWidget {
|
||||
BackupPage({Key? key}) : super(key: key);
|
||||
|
||||
final _setting = locator<SettingStore>();
|
||||
final _server = locator<ServerStore>();
|
||||
final _snippet = locator<SnippetStore>();
|
||||
final _privateKey = locator<PrivateKeyStore>();
|
||||
final _dockerHosts = locator<DockerStore>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -33,7 +36,7 @@ class BackupPage extends StatelessWidget {
|
||||
final s = S.of(context)!;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(s.importAndExport, style: textSize18),
|
||||
title: Text(s.backupAndRestore, style: textSize18),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
@@ -50,8 +53,20 @@ class BackupPage extends StatelessWidget {
|
||||
const SizedBox(
|
||||
height: 107,
|
||||
),
|
||||
_buildCard(s.restore, Icons.download, media,
|
||||
() => _showImportDialog(context, s)),
|
||||
_buildCard(s.restore, Icons.download, media, () async {
|
||||
final path = await pickOneFile();
|
||||
if (path == null) {
|
||||
showSnackBar(context, Text(s.notSelected));
|
||||
return;
|
||||
}
|
||||
final file = File(path);
|
||||
if (!file.existsSync()) {
|
||||
showSnackBar(context, Text(s.fileNotExist(path)));
|
||||
return;
|
||||
}
|
||||
final text = await file.readAsString();
|
||||
_import(text, context, s);
|
||||
}),
|
||||
const SizedBox(height: 7),
|
||||
const Divider(),
|
||||
const SizedBox(height: 7),
|
||||
@@ -59,7 +74,23 @@ class BackupPage extends StatelessWidget {
|
||||
s.backup,
|
||||
Icons.file_upload,
|
||||
media,
|
||||
() => _showExportDialog(context, s),
|
||||
() async {
|
||||
final result = _diyEncrtpt(
|
||||
json.encode(
|
||||
Backup(
|
||||
backupFormatVersion,
|
||||
DateTime.now().toString().split('.').first,
|
||||
_server.fetch(),
|
||||
_snippet.fetch(),
|
||||
_privateKey.fetch(),
|
||||
_dockerHosts.fetch(),
|
||||
),
|
||||
),
|
||||
);
|
||||
final path = '${(await docDir).path}/srvbox_bak.json';
|
||||
await File(path).writeAsString(result);
|
||||
await shareFiles(context, [path]);
|
||||
},
|
||||
)
|
||||
],
|
||||
)),
|
||||
@@ -93,77 +124,12 @@ class BackupPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showExportDialog(BuildContext context, S s) async {
|
||||
final exportFieldController = TextEditingController()
|
||||
..text = _diyEncrtpt(
|
||||
json.encode(
|
||||
Backup(
|
||||
backupFormatVersion,
|
||||
DateTime.now().toString().split('.').first,
|
||||
_server.fetch(),
|
||||
_snippet.fetch(),
|
||||
_privateKey.fetch(),
|
||||
_setting.primaryColor.fetch() ?? Colors.pinkAccent.value,
|
||||
_setting.serverStatusUpdateInterval.fetch() ?? 2,
|
||||
_setting.launchPage.fetch() ?? 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
await showRoundDialog(
|
||||
context,
|
||||
s.export,
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'JSON',
|
||||
),
|
||||
maxLines: 7,
|
||||
controller: exportFieldController,
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
child: Text(s.copy),
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: exportFieldController.text));
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showImportDialog(BuildContext context, S s) async {
|
||||
final importFieldController = TextEditingController();
|
||||
await showRoundDialog(
|
||||
context,
|
||||
s.import,
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'JSON',
|
||||
),
|
||||
maxLines: 3,
|
||||
controller: importFieldController,
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async =>
|
||||
await _import(importFieldController.text.trim(), context, s),
|
||||
child: const Text('GO'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _import(String text, BuildContext context, S s) async {
|
||||
if (text.isEmpty) {
|
||||
showSnackBar(context, Text(s.fieldMustNotEmpty));
|
||||
return;
|
||||
}
|
||||
_importBackup(text, context, s);
|
||||
Navigator.of(context).pop();
|
||||
await _importBackup(text, context, s);
|
||||
}
|
||||
|
||||
Future<void> _importBackup(String raw, BuildContext context, S s) async {
|
||||
@@ -180,7 +146,7 @@ class BackupPage extends StatelessWidget {
|
||||
Text(s.restoreSureWithDate(backup.date)),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
@@ -194,19 +160,32 @@ class BackupPage extends StatelessWidget {
|
||||
for (final s in backup.keys) {
|
||||
_privateKey.put(s);
|
||||
}
|
||||
_setting.primaryColor.put(backup.primaryColor);
|
||||
_setting.serverStatusUpdateInterval
|
||||
.put(backup.serverStatusUpdateInterval);
|
||||
_setting.launchPage.put(backup.launchPage);
|
||||
Navigator.of(context).pop();
|
||||
showSnackBar(context, Text(s.restoreSuccess));
|
||||
for (final k in backup.dockerHosts.keys) {
|
||||
_dockerHosts.setDockerHost(k, backup.dockerHosts[k]!);
|
||||
}
|
||||
context.pop();
|
||||
showRoundDialog(
|
||||
context,
|
||||
s.attention,
|
||||
Text(s.restoreSuccess),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => rebuildAll(context),
|
||||
child: Text(s.restart),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(s.cancel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Text(s.ok),
|
||||
),
|
||||
],
|
||||
);
|
||||
} catch (e) {
|
||||
showSnackBar(context, Text(s.invalidJson));
|
||||
showSnackBar(context, Text(e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../../core/utils/ui.dart';
|
||||
import '../../data/model/docker/ps.dart';
|
||||
@@ -8,8 +9,8 @@ import '../../data/model/server/server_private_info.dart';
|
||||
import '../../data/provider/docker.dart';
|
||||
import '../../data/provider/server.dart';
|
||||
import '../../data/res/error.dart';
|
||||
import '../../data/res/font_style.dart';
|
||||
import '../../data/res/menu.dart';
|
||||
import '../../data/res/ui.dart';
|
||||
import '../../data/res/url.dart';
|
||||
import '../../data/store/docker.dart';
|
||||
import '../../locator.dart';
|
||||
@@ -47,13 +48,10 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final client = locator<ServerProvider>()
|
||||
.servers
|
||||
.firstWhere((element) => element.spi == widget.spi)
|
||||
.client;
|
||||
final client = locator<ServerProvider>().getServer(widget.spi.id).client;
|
||||
if (client == null) {
|
||||
showSnackBar(context, Text(_s.noClient));
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
return;
|
||||
}
|
||||
_docker.init(client, widget.spi.user, onPwdRequest, widget.spi.id);
|
||||
@@ -122,12 +120,12 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
await _showAddCmdPreview(
|
||||
_buildAddCmd(
|
||||
imageCtrl.text.trim(),
|
||||
@@ -149,12 +147,12 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
Text(cmd),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
final result = await _docker.run(cmd);
|
||||
if (result != null) {
|
||||
showSnackBar(context, Text(getErrMsg(result) ?? _s.unknownError));
|
||||
@@ -190,13 +188,13 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
if (_textController.text == '') {
|
||||
showRoundDialog(context, _s.attention, Text(_s.fieldMustNotEmpty), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
}
|
||||
|
||||
Future<String> onPwdRequest() async {
|
||||
@@ -216,8 +214,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
context.pop();
|
||||
},
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
@@ -297,12 +295,12 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
Text(_s.sureDelete(e.repo)),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
final result = await _docker.run(
|
||||
'docker rmi ${e.id} -f',
|
||||
);
|
||||
@@ -378,12 +376,12 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
onSubmitted: (value) {
|
||||
locator<DockerStore>().setDockerHost(widget.spi.id, value.trim());
|
||||
_docker.refresh();
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
],
|
||||
@@ -481,7 +479,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
_docker.delete(dItem.containerId);
|
||||
},
|
||||
child: Text(_s.ok),
|
||||
|
||||
@@ -2,18 +2,21 @@ import 'package:after_layout/after_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
import 'package:toolbox/data/provider/app.dart';
|
||||
import 'package:toolbox/data/res/misc.dart';
|
||||
|
||||
import '../../core/analysis.dart';
|
||||
import '../../core/route.dart';
|
||||
import '../../core/update.dart';
|
||||
import '../../core/utils/platform.dart';
|
||||
import '../../core/utils/ui.dart';
|
||||
import '../../data/model/app/dynamic_color.dart';
|
||||
import '../../data/model/app/navigation_item.dart';
|
||||
import '../../data/provider/server.dart';
|
||||
import '../../data/res/build_data.dart';
|
||||
import '../../data/res/font_style.dart';
|
||||
import '../../data/res/icon.dart';
|
||||
import '../../data/res/tab.dart';
|
||||
import '../../data/res/ui.dart';
|
||||
import '../../data/res/url.dart';
|
||||
import '../../data/store/setting.dart';
|
||||
import '../../locator.dart';
|
||||
@@ -43,8 +46,11 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
AutomaticKeepAliveClientMixin,
|
||||
AfterLayoutMixin,
|
||||
WidgetsBindingObserver {
|
||||
late final ServerProvider _serverProvider;
|
||||
final _serverProvider = locator<ServerProvider>();
|
||||
final _setting = locator<SettingStore>();
|
||||
|
||||
late final PageController _pageController;
|
||||
|
||||
late int _selectIndex;
|
||||
late double _width;
|
||||
late S _s;
|
||||
@@ -52,9 +58,8 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_serverProvider = locator<ServerProvider>();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_selectIndex = locator<SettingStore>().launchPage.fetch()!;
|
||||
_selectIndex = _setting.launchPage.fetch()!;
|
||||
_pageController = PageController(initialPage: _selectIndex);
|
||||
}
|
||||
|
||||
@@ -75,12 +80,27 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.paused) {
|
||||
_serverProvider.setDisconnected();
|
||||
_serverProvider.stopAutoRefresh();
|
||||
}
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_serverProvider.startAutoRefresh();
|
||||
if (isDesktop) return;
|
||||
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
if (isIOS) {
|
||||
_serverProvider.startAutoRefresh();
|
||||
}
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
// Keep running in background on Android device
|
||||
if (isAndroid && _setting.bgRun.fetch()!) {
|
||||
if (locator<AppProvider>().moveBg) {
|
||||
bgRunChannel.invokeMethod('sendToBackground');
|
||||
}
|
||||
} else {
|
||||
_serverProvider.setDisconnected();
|
||||
_serverProvider.stopAutoRefresh();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +254,7 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
child: Text(_s.feedback),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.close),
|
||||
)
|
||||
],
|
||||
@@ -246,27 +266,44 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
onTap: () => AppRoute(const SnippetListPage(), 'snippet list')
|
||||
.go(context),
|
||||
),
|
||||
AboutListTile(
|
||||
icon: const Icon(Icons.text_snippet),
|
||||
applicationName: '\n${BuildData.name}',
|
||||
applicationVersion: _versionStr,
|
||||
applicationIcon: _buildIcon(),
|
||||
aboutBoxChildren: [
|
||||
UrlText(
|
||||
text: _s.madeWithLove(myGithub),
|
||||
replace: 'lollipopkit'),
|
||||
UrlText(
|
||||
text: _s.aboutThanks,
|
||||
),
|
||||
// Thanks
|
||||
...thanksMap.keys.map(
|
||||
(key) => UrlText(
|
||||
text: thanksMap[key] ?? '',
|
||||
replace: key,
|
||||
ListTile(
|
||||
leading: const Icon(Icons.text_snippet),
|
||||
title: Text(_s.about),
|
||||
onTap: () {
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.about,
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UrlText(
|
||||
text: _s.madeWithLove(myGithub),
|
||||
replace: 'lollipopkit'),
|
||||
UrlText(
|
||||
text: _s.aboutThanks,
|
||||
),
|
||||
// Thanks
|
||||
...thanksMap.keys.map(
|
||||
(key) => UrlText(
|
||||
text: thanksMap[key] ?? '',
|
||||
replace: key,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
child: Text(_s.about),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => showLicensePage(context: context),
|
||||
child: Text(_s.license),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.close),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
@@ -308,8 +345,11 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
@override
|
||||
Future<void> afterFirstLayout(BuildContext context) async {
|
||||
await GetIt.I.allReady();
|
||||
await locator<ServerProvider>().loadLocalData();
|
||||
await _serverProvider.loadLocalData();
|
||||
await _serverProvider.refreshData();
|
||||
await doUpdate(context);
|
||||
await Analysis.init();
|
||||
if (!Analysis.enabled) {
|
||||
await Analysis.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import '../../core/utils/ui.dart';
|
||||
import '../../data/model/server/ping_result.dart';
|
||||
import '../../data/provider/server.dart';
|
||||
import '../../data/res/color.dart';
|
||||
import '../../data/res/font_style.dart';
|
||||
import '../../data/res/ui.dart';
|
||||
import '../../locator.dart';
|
||||
import '../widget/input_field.dart';
|
||||
import '../widget/round_rect_card.dart';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../../data/model/pkg/upgrade_info.dart';
|
||||
import '../../data/model/server/dist.dart';
|
||||
@@ -8,7 +9,7 @@ import '../../core/utils/ui.dart';
|
||||
import '../../data/model/server/server_private_info.dart';
|
||||
import '../../data/provider/pkg.dart';
|
||||
import '../../data/provider/server.dart';
|
||||
import '../../data/res/font_style.dart';
|
||||
import '../../data/res/ui.dart';
|
||||
import '../../locator.dart';
|
||||
import '../widget/center_loading.dart';
|
||||
import '../widget/round_rect_card.dart';
|
||||
@@ -48,12 +49,10 @@ class _PkgManagePageState extends State<PkgManagePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final si = locator<ServerProvider>()
|
||||
.servers
|
||||
.firstWhere((e) => e.spi == widget.spi);
|
||||
final si = locator<ServerProvider>().getServer(widget.spi.id);
|
||||
if (si.client == null) {
|
||||
showSnackBar(context, Text(_s.waitConnection));
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,14 +77,14 @@ class _PkgManagePageState extends State<PkgManagePage>
|
||||
Text(_s.fieldMustNotEmpty),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
}
|
||||
|
||||
Future<String> onPwdRequest() async {
|
||||
@@ -105,8 +104,8 @@ class _PkgManagePageState extends State<PkgManagePage>
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
context.pop();
|
||||
},
|
||||
child: Text(_s.cancel)),
|
||||
TextButton(
|
||||
@@ -179,8 +178,9 @@ class _PkgManagePageState extends State<PkgManagePage>
|
||||
}
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(13),
|
||||
children:
|
||||
[_buildUpdatePanel(apt)].map((e) => RoundRectCard(e)).toList(),
|
||||
children: [
|
||||
_buildUpdatePanel(apt),
|
||||
].map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -7,13 +7,14 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/core/extension/numx.dart';
|
||||
import 'package:toolbox/core/utils/misc.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
import 'package:toolbox/data/res/misc.dart';
|
||||
|
||||
import '../../../core/utils/server.dart';
|
||||
import '../../../core/utils/ui.dart';
|
||||
import '../../../data/model/server/private_key_info.dart';
|
||||
import '../../../data/provider/private_key.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../../locator.dart';
|
||||
import '../../widget/input_decoration.dart';
|
||||
|
||||
@@ -68,7 +69,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
|
||||
tooltip: _s.delete,
|
||||
onPressed: () {
|
||||
_provider.delInfo(widget.info!);
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
icon: const Icon(Icons.delete))
|
||||
: const SizedBox()
|
||||
@@ -176,7 +177,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
|
||||
} else {
|
||||
_provider.addInfo(info);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
child: const Icon(Icons.save),
|
||||
),
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../core/route.dart';
|
||||
import '../../../data/provider/private_key.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import 'edit.dart';
|
||||
import '../../../view/widget/round_rect_card.dart';
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ import '../../../data/model/server/server.dart';
|
||||
import '../../../data/model/server/server_status.dart';
|
||||
import '../../../data/provider/server.dart';
|
||||
import '../../../data/res/color.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/padding.dart';
|
||||
import '../../../data/res/sizedbox.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../../data/store/setting.dart';
|
||||
import '../../../locator.dart';
|
||||
import '../../widget/round_rect_card.dart';
|
||||
@@ -43,9 +41,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ServerProvider>(builder: (_, provider, __) {
|
||||
return _buildMainPage(
|
||||
provider.servers.firstWhere(
|
||||
(e) => e.spi.id == widget.id,
|
||||
),
|
||||
provider.getServer(widget.id),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:after_layout/after_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../../../core/route.dart';
|
||||
import '../../../core/utils/ui.dart';
|
||||
@@ -10,7 +11,7 @@ import '../../../data/model/server/server_private_info.dart';
|
||||
import '../../../data/provider/private_key.dart';
|
||||
import '../../../data/provider/server.dart';
|
||||
import '../../../data/res/color.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../../data/store/private_key.dart';
|
||||
import '../../../locator.dart';
|
||||
import '../../widget/input_decoration.dart';
|
||||
@@ -74,8 +75,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_serverProvider.delServer(widget.spi!.id);
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
context.pop();
|
||||
},
|
||||
child: Text(
|
||||
_s.ok,
|
||||
@@ -83,7 +84,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
)
|
||||
],
|
||||
@@ -232,11 +233,11 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
Text(_s.sureNoPwd),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
onPressed: () => context.pop(false),
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
onPressed: () => context.pop(true),
|
||||
child: Text(_s.cancel),
|
||||
)
|
||||
],
|
||||
@@ -277,7 +278,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
_serverProvider.updateServer(widget.spi!, spi);
|
||||
}
|
||||
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../../../core/route.dart';
|
||||
import '../../../core/utils/ui.dart';
|
||||
@@ -11,21 +12,18 @@ import '../../../data/model/server/server.dart';
|
||||
import '../../../data/model/server/server_private_info.dart';
|
||||
import '../../../data/model/server/server_status.dart';
|
||||
import '../../../data/provider/server.dart';
|
||||
import '../../../data/provider/snippet.dart';
|
||||
import '../../../data/res/color.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/menu.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../../data/res/url.dart';
|
||||
import '../../../data/store/setting.dart';
|
||||
import '../../../locator.dart';
|
||||
import '../../widget/dropdown_menu.dart';
|
||||
import '../../widget/picker.dart';
|
||||
import '../../widget/round_rect_card.dart';
|
||||
import '../../widget/url_text.dart';
|
||||
import '../docker.dart';
|
||||
import '../pkg.dart';
|
||||
import '../sftp/view.dart';
|
||||
import '../snippet/edit.dart';
|
||||
import '../ssh.dart';
|
||||
import 'detail.dart';
|
||||
import 'edit.dart';
|
||||
@@ -118,7 +116,7 @@ class _ServerPageState extends State<ServerPage>
|
||||
.go(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(13),
|
||||
child: _buildRealServerCard(si.status, si.spi.name, si.cs, si.spi),
|
||||
child: _buildRealServerCard(si.status, si.spi.name, si.state, si.spi),
|
||||
),
|
||||
onTap: () => AppRoute(ServerDetailPage(si.spi.id), 'server detail page')
|
||||
.go(context),
|
||||
@@ -225,7 +223,7 @@ class _ServerPageState extends State<ServerPage>
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
AppRoute(SSHPage(spi: spi), 'ssh page').go(context);
|
||||
},
|
||||
child: Text(_s.ok),
|
||||
@@ -266,7 +264,21 @@ class _ServerPageState extends State<ServerPage>
|
||||
AppRoute(SFTPPage(spi), 'SFTP').go(context);
|
||||
break;
|
||||
case ServerTabMenuItems.snippet:
|
||||
_showSnippetDialog(spi.id);
|
||||
showSnippetDialog(context, _s, (s) async {
|
||||
final result =
|
||||
await locator<ServerProvider>().runSnippet(spi.id, s);
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.result,
|
||||
Text(result ?? _s.error, style: textSize13),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
break;
|
||||
case ServerTabMenuItems.edit:
|
||||
AppRoute(ServerEditPage(spi: spi), 'Edit server info').go(context);
|
||||
@@ -382,69 +394,15 @@ class _ServerPageState extends State<ServerPage>
|
||||
);
|
||||
}
|
||||
|
||||
void _showSnippetDialog(String id) {
|
||||
final provider = locator<SnippetProvider>();
|
||||
if (provider.snippets.isEmpty) {
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.attention,
|
||||
Text(_s.noSavedSnippet),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
AppRoute(const SnippetEditPage(), 'edit snippet').go(context);
|
||||
},
|
||||
child: Text(_s.addOne),
|
||||
)
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var snippet = provider.snippets.first;
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.choose,
|
||||
buildPicker(
|
||||
provider.snippets.map((e) => Text(e.name)).toList(),
|
||||
(idx) => snippet = provider.snippets[idx],
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
final result = await _serverProvider.runSnippet(id, snippet);
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.result,
|
||||
Text(result ?? _s.error, style: textSize13),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(_s.ok),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Text(_s.run),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Future<void> afterFirstLayout(BuildContext context) async {
|
||||
await GetIt.I.allReady();
|
||||
await _serverProvider.loadLocalData();
|
||||
await _serverProvider.refreshData();
|
||||
if (_serverProvider.servers.isEmpty) {
|
||||
await _serverProvider.loadLocalData();
|
||||
}
|
||||
_serverProvider.startAutoRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../core/utils/misc.dart';
|
||||
import '../../core/utils/platform.dart';
|
||||
import '../../data/model/ssh/terminal_color.dart';
|
||||
import '../../core/update.dart';
|
||||
import '../../core/utils/ui.dart';
|
||||
@@ -10,10 +14,12 @@ import '../../data/provider/app.dart';
|
||||
import '../../data/provider/server.dart';
|
||||
import '../../data/res/build_data.dart';
|
||||
import '../../data/res/color.dart';
|
||||
import '../../data/res/font_style.dart';
|
||||
import '../../data/res/path.dart';
|
||||
import '../../data/res/tab.dart';
|
||||
import '../../data/res/ui.dart';
|
||||
import '../../data/store/setting.dart';
|
||||
import '../../locator.dart';
|
||||
import '../widget/future_widget.dart';
|
||||
import '../widget/round_rect_card.dart';
|
||||
|
||||
class SettingPage extends StatefulWidget {
|
||||
@@ -36,6 +42,8 @@ class _SettingPageState extends State<SettingPage> {
|
||||
late double _maxRetryCount;
|
||||
late double _updateInterval;
|
||||
|
||||
String? _pushToken;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
@@ -50,9 +58,10 @@ class _SettingPageState extends State<SettingPage> {
|
||||
_setting = locator<SettingStore>();
|
||||
_launchPageIdx = _setting.launchPage.fetch()!;
|
||||
_termThemeIdx = _setting.termColorIdx.fetch()!;
|
||||
_nightMode = _setting.nightMode.fetch()!;
|
||||
_nightMode = _setting.themeMode.fetch()!;
|
||||
_updateInterval = _setting.serverStatusUpdateInterval.fetch()!.toDouble();
|
||||
_maxRetryCount = _setting.maxRetryCount.fetch()!.toDouble();
|
||||
_selectedColorValue = _setting.primaryColor.fetch()!;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -70,6 +79,10 @@ class _SettingPageState extends State<SettingPage> {
|
||||
// Server
|
||||
_buildTitle(_s.server),
|
||||
_buildServer(),
|
||||
// SSH
|
||||
_buildTitle('SSH'),
|
||||
_buildSSH(),
|
||||
const SizedBox(height: 37),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -81,20 +94,27 @@ class _SettingPageState extends State<SettingPage> {
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: textSize13,
|
||||
style: grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildApp() {
|
||||
final children = [
|
||||
_buildThemeMode(),
|
||||
_buildAppColorPreview(),
|
||||
_buildLaunchPage(),
|
||||
_buildCheckUpdate(),
|
||||
];
|
||||
if (isIOS) {
|
||||
children.add(_buildPushToken());
|
||||
}
|
||||
if (isAndroid) {
|
||||
children.add(_buildBgRun());
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
_buildNightMode(),
|
||||
_buildAppColorPreview(),
|
||||
_buildLaunchPage(),
|
||||
_buildCheckUpdate(),
|
||||
].map((e) => RoundRectCard(e)).toList(),
|
||||
children: children.map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,21 +123,28 @@ class _SettingPageState extends State<SettingPage> {
|
||||
children: [
|
||||
_buildDistLogoSwitch(),
|
||||
_buildUpdateInterval(),
|
||||
_buildTermTheme(),
|
||||
_buildMaxRetry(),
|
||||
].map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSSH() {
|
||||
return Column(
|
||||
children: [
|
||||
_buildTermTheme(),
|
||||
_buildFont(),
|
||||
].map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDistLogoSwitch() {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
_s.showDistLogo,
|
||||
style: textSize13,
|
||||
),
|
||||
subtitle: Text(
|
||||
_s.onServerDetailPage,
|
||||
style: textSize13Grey,
|
||||
style: grey,
|
||||
),
|
||||
trailing: buildSwitch(context, _setting.showDistLogo),
|
||||
);
|
||||
@@ -140,8 +167,6 @@ class _SettingPageState extends State<SettingPage> {
|
||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||
title: Text(
|
||||
display,
|
||||
style: textSize13,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
onTap: () => doUpdate(context, force: true),
|
||||
);
|
||||
@@ -154,16 +179,13 @@ class _SettingPageState extends State<SettingPage> {
|
||||
textColor: primaryColor,
|
||||
title: Text(
|
||||
_s.updateServerStatusInterval,
|
||||
style: textSize13,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
subtitle: Text(
|
||||
_s.willTakEeffectImmediately,
|
||||
style: textSize13Grey,
|
||||
style: grey,
|
||||
),
|
||||
trailing: Text(
|
||||
'${_updateInterval.toInt()} ${_s.second}',
|
||||
style: textSize13,
|
||||
),
|
||||
children: [
|
||||
Slider(
|
||||
@@ -190,7 +212,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
_updateInterval == 0.0
|
||||
? Text(
|
||||
_s.updateIntervalEqual0,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
style: grey,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const SizedBox(),
|
||||
@@ -213,7 +235,6 @@ class _SettingPageState extends State<SettingPage> {
|
||||
),
|
||||
title: Text(
|
||||
_s.appPrimaryColor,
|
||||
style: textSize13,
|
||||
),
|
||||
children: [_buildAppColorPicker(), _buildColorPickerConfirmBtn()],
|
||||
);
|
||||
@@ -234,7 +255,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: (() {
|
||||
_setting.primaryColor.put(_selectedColorValue);
|
||||
setState(() {});
|
||||
_showRestartSnackbar();
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -245,13 +266,11 @@ class _SettingPageState extends State<SettingPage> {
|
||||
textColor: primaryColor,
|
||||
title: Text(
|
||||
_s.launchPage,
|
||||
style: textSize13,
|
||||
),
|
||||
trailing: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: _media.size.width * 0.35),
|
||||
child: Text(
|
||||
tabTitleName(context, _launchPageIdx),
|
||||
style: textSize13,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
@@ -261,7 +280,6 @@ class _SettingPageState extends State<SettingPage> {
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
tabTitleName(context, tabs.indexOf(e)),
|
||||
style: textSize13,
|
||||
),
|
||||
trailing: _buildRadio(tabs.indexOf(e)),
|
||||
),
|
||||
@@ -289,11 +307,9 @@ class _SettingPageState extends State<SettingPage> {
|
||||
childrenPadding: const EdgeInsets.only(left: 17),
|
||||
title: Text(
|
||||
_s.termTheme,
|
||||
style: textSize13,
|
||||
),
|
||||
trailing: Text(
|
||||
TerminalColorsPlatform.values[_termThemeIdx].name,
|
||||
style: textSize13,
|
||||
),
|
||||
children: _buildTermThemeRadioList(),
|
||||
);
|
||||
@@ -306,7 +322,6 @@ class _SettingPageState extends State<SettingPage> {
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
e.name,
|
||||
style: textSize13,
|
||||
),
|
||||
trailing: _buildTermThemeRadio(e),
|
||||
),
|
||||
@@ -333,12 +348,10 @@ class _SettingPageState extends State<SettingPage> {
|
||||
textColor: primaryColor,
|
||||
title: Text(
|
||||
_s.maxRetryCount,
|
||||
style: textSize13,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
trailing: Text(
|
||||
'${_maxRetryCount.toInt()} ${_s.times}',
|
||||
style: textSize13,
|
||||
),
|
||||
children: [
|
||||
Slider(
|
||||
@@ -364,7 +377,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
_maxRetryCount == 0.0
|
||||
? Text(
|
||||
_s.maxRetryCountEqual0,
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
style: grey,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const SizedBox(),
|
||||
@@ -375,17 +388,14 @@ class _SettingPageState extends State<SettingPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNightMode() {
|
||||
Widget _buildThemeMode() {
|
||||
return ExpansionTile(
|
||||
textColor: primaryColor,
|
||||
title: Text(
|
||||
_s.themeMode,
|
||||
style: textSize13,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
trailing: Text(
|
||||
_buildNightModeStr(_nightMode),
|
||||
style: textSize13,
|
||||
_buildThemeModeStr(_nightMode),
|
||||
),
|
||||
children: [
|
||||
Slider(
|
||||
@@ -400,16 +410,16 @@ class _SettingPageState extends State<SettingPage> {
|
||||
});
|
||||
},
|
||||
onChangeEnd: (val) {
|
||||
_setting.nightMode.put(val.toInt());
|
||||
_setting.themeMode.put(val.toInt());
|
||||
},
|
||||
label: _buildNightModeStr(_nightMode),
|
||||
label: _buildThemeModeStr(_nightMode),
|
||||
divisions: 2,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _buildNightModeStr(int n) {
|
||||
String _buildThemeModeStr(int n) {
|
||||
switch (n) {
|
||||
case 1:
|
||||
return _s.light;
|
||||
@@ -419,4 +429,100 @@ class _SettingPageState extends State<SettingPage> {
|
||||
return _s.auto;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildPushToken() {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
_s.pushToken,
|
||||
),
|
||||
trailing: TextButton(
|
||||
child: Text(_s.copy),
|
||||
onPressed: () {
|
||||
if (_pushToken != null) {
|
||||
copy(_pushToken!);
|
||||
showSnackBar(context, Text(_s.success));
|
||||
} else {
|
||||
showSnackBar(context, Text(_s.getPushTokenFailed));
|
||||
}
|
||||
},
|
||||
),
|
||||
subtitle: FutureWidget<String?>(
|
||||
future: getToken(),
|
||||
loading: Text(_s.gettingToken),
|
||||
error: (error, trace) => Text('${_s.error}: $error'),
|
||||
noData: Text(_s.nullToken),
|
||||
success: (text) {
|
||||
_pushToken = text;
|
||||
return Text(
|
||||
text ?? _s.nullToken,
|
||||
style: grey,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFont() {
|
||||
return ExpansionTile(
|
||||
title: Text(_s.chooseFontFile),
|
||||
trailing: Text(getFileName(_setting.fontPath.fetch()) ?? _s.notSelected),
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async => _pickFontFile(),
|
||||
child: Text(_s.pickFile),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => setState(() {
|
||||
_setting.fontPath.delete();
|
||||
_showRestartSnackbar();
|
||||
}),
|
||||
child: Text(_s.clear),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickFontFile() async {
|
||||
final path = await pickOneFile();
|
||||
if (path != null) {
|
||||
// iOS can't copy file to app dir, so we need to use the original path
|
||||
if (isIOS) {
|
||||
_setting.fontPath.put(path);
|
||||
} else {
|
||||
final fontDir_ = await fontDir;
|
||||
final fontFile = File(path);
|
||||
final newPath = '${fontDir_.path}/${path.split('/').last}';
|
||||
await fontFile.copy(newPath);
|
||||
_setting.fontPath.put(newPath);
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
_showRestartSnackbar();
|
||||
return;
|
||||
}
|
||||
showSnackBar(context, Text(_s.failed));
|
||||
}
|
||||
|
||||
void _showRestartSnackbar() {
|
||||
showSnackBarWithAction(
|
||||
context,
|
||||
'${_s.success}\n${_s.needRestart}',
|
||||
_s.restart,
|
||||
() => rebuildAll(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBgRun() {
|
||||
return ListTile(
|
||||
title: Text(_s.bgRun),
|
||||
trailing: buildSwitch(context, _setting.bgRun),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../../../core/extension/numx.dart';
|
||||
import '../../../core/extension/stringx.dart';
|
||||
@@ -9,8 +10,8 @@ import '../../../core/route.dart';
|
||||
import '../../../core/utils/misc.dart';
|
||||
import '../../../core/utils/ui.dart';
|
||||
import '../../../data/model/app/path_with_prefix.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/path.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../widget/fade_in.dart';
|
||||
import 'downloading.dart';
|
||||
|
||||
@@ -29,7 +30,7 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
sftpDownloadDir.then((dir) {
|
||||
sftpDir.then((dir) {
|
||||
_path = PathWithPrefix(dir.path);
|
||||
_prefixPath = '${dir.path}/';
|
||||
setState(() {});
|
||||
@@ -144,21 +145,21 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(_s.delete),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.sureDelete(fileName),
|
||||
const SizedBox(),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
file.deleteSync();
|
||||
setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
@@ -177,7 +178,7 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: (() => Navigator.of(context).pop()),
|
||||
onPressed: (() => context.pop()),
|
||||
child: Text(_s.close),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ import '../../../core/utils/misc.dart';
|
||||
import '../../../core/utils/ui.dart';
|
||||
import '../../../data/model/sftp/download_status.dart';
|
||||
import '../../../data/provider/sftp_download.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../widget/center_loading.dart';
|
||||
import '../../widget/round_rect_card.dart';
|
||||
|
||||
@@ -41,7 +41,7 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return Consumer<SftpDownloadProvider>(builder: (__, pro, _) {
|
||||
return Consumer<SftpProvider>(builder: (__, pro, _) {
|
||||
if (pro.status.isEmpty) {
|
||||
return Center(
|
||||
child: Text(_s.sftpNoDownloadTask),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../../../core/extension/numx.dart';
|
||||
import '../../../core/extension/stringx.dart';
|
||||
@@ -18,6 +19,7 @@ import '../../../data/provider/sftp_download.dart';
|
||||
import '../../../data/res/path.dart';
|
||||
import '../../../data/store/private_key.dart';
|
||||
import '../../../locator.dart';
|
||||
import '../../widget/center_loading.dart';
|
||||
import '../../widget/fade_in.dart';
|
||||
import '../../widget/two_line_text.dart';
|
||||
import 'downloading.dart';
|
||||
@@ -51,7 +53,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
final serverProvider = locator<ServerProvider>();
|
||||
_si = serverProvider.servers.firstWhere((s) => s.spi == widget.spi);
|
||||
_si = serverProvider.getServer(widget.spi.id);
|
||||
_client = _si?.client;
|
||||
}
|
||||
|
||||
@@ -113,7 +115,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.close),
|
||||
)
|
||||
],
|
||||
@@ -134,14 +136,13 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
labelText: _s.path,
|
||||
hintText: '/',
|
||||
),
|
||||
onSubmitted: (value) =>
|
||||
Navigator.of(context).pop(value),
|
||||
onSubmitted: (value) => context.pop(value),
|
||||
),
|
||||
],
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
)
|
||||
],
|
||||
@@ -177,7 +178,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
);
|
||||
|
||||
Widget _buildFileView() {
|
||||
if (_client == null || _si?.cs != ServerState.connected) {
|
||||
if (_client == null || _si?.state != ServerState.connected) {
|
||||
return centerCircleLoading;
|
||||
}
|
||||
|
||||
@@ -279,7 +280,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
)
|
||||
],
|
||||
@@ -293,19 +294,17 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Text('${_s.dl2Local(name.filename)}\n${_s.keepForeground}'),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
final prePath = _status.path!.path;
|
||||
final remotePath =
|
||||
prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
|
||||
final local = '${(await sftpDownloadDir).path}$remotePath';
|
||||
context.pop();
|
||||
final remotePath = _getRemotePath(name);
|
||||
final local = '${(await sftpDir).path}$remotePath';
|
||||
final pubKeyId = widget.spi.pubKeyId;
|
||||
|
||||
locator<SftpDownloadProvider>().add(
|
||||
locator<SftpProvider>().add(
|
||||
DownloadItem(
|
||||
widget.spi,
|
||||
remotePath,
|
||||
@@ -316,7 +315,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
: locator<PrivateKeyStore>().get(pubKeyId).privateKey,
|
||||
);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
child: Text(_s.download),
|
||||
)
|
||||
@@ -325,20 +324,46 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
|
||||
void delete(BuildContext context, SftpName file) {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
final isDir = file.attr.isDirectory;
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.attention,
|
||||
Text(_s.sureDelete(file.filename)),
|
||||
Text(
|
||||
'${_s.sureDelete(file.filename)}${isDir ? '\n${_s.sureDirEmpty}' : ''}'),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_status.client!.remove(file.filename);
|
||||
Navigator.of(context).pop();
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
showRoundDialog(context, 'Waiting...', centerSizedLoading, [],
|
||||
barrierDismiss: false);
|
||||
final remotePath = _getRemotePath(file);
|
||||
try {
|
||||
if (file.attr.isDirectory) {
|
||||
await _status.client!.rmdir(remotePath);
|
||||
} else {
|
||||
await _status.client!.remove(remotePath);
|
||||
}
|
||||
context.pop();
|
||||
} catch (e) {
|
||||
context.pop();
|
||||
showRoundDialog(
|
||||
context,
|
||||
_s.attention,
|
||||
Text(e.toString()),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
)
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
listDir();
|
||||
},
|
||||
child: Text(
|
||||
@@ -351,7 +376,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
|
||||
void mkdir(BuildContext context) {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
final textController = TextEditingController();
|
||||
showRoundDialog(
|
||||
context,
|
||||
@@ -364,7 +389,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
@@ -376,7 +401,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Text(_s.fieldMustNotEmpty),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
],
|
||||
@@ -385,7 +410,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
_status.client!
|
||||
.mkdir('${_status.path!.path}/${textController.text}');
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
listDir();
|
||||
},
|
||||
child: Text(
|
||||
@@ -398,7 +423,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
|
||||
void newFile(BuildContext context) {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
final textController = TextEditingController();
|
||||
showRoundDialog(
|
||||
context,
|
||||
@@ -411,7 +436,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.cancel),
|
||||
),
|
||||
TextButton(
|
||||
@@ -423,7 +448,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Text(_s.fieldMustNotEmpty),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
],
|
||||
@@ -433,7 +458,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
(await _status.client!
|
||||
.open('${_status.path!.path}/${textController.text}'))
|
||||
.writeBytes(Uint8List(0));
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
listDir();
|
||||
},
|
||||
child: Text(
|
||||
@@ -446,7 +471,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
|
||||
void rename(BuildContext context, SftpName file) {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
final textController = TextEditingController();
|
||||
showRoundDialog(
|
||||
context,
|
||||
@@ -458,9 +483,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
),
|
||||
),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(_s.cancel)),
|
||||
TextButton(onPressed: () => context.pop(), child: Text(_s.cancel)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (textController.text == '') {
|
||||
@@ -470,7 +493,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Text(_s.fieldMustNotEmpty),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
),
|
||||
],
|
||||
@@ -478,7 +501,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
return;
|
||||
}
|
||||
await _status.client!.rename(file.filename, textController.text);
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
listDir();
|
||||
},
|
||||
child: Text(
|
||||
@@ -490,6 +513,11 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
);
|
||||
}
|
||||
|
||||
String _getRemotePath(SftpName name) {
|
||||
final prePath = _status.path!.path;
|
||||
return prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
|
||||
}
|
||||
|
||||
Future<void> listDir({String? path, SSHClient? client}) async {
|
||||
if (_status.isBusy) {
|
||||
return;
|
||||
@@ -517,7 +545,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
Text(e.toString()),
|
||||
[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.ok),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'package:after_layout/after_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
|
||||
import '../../../core/utils/ui.dart';
|
||||
import '../../../data/model/server/snippet.dart';
|
||||
import '../../../data/provider/snippet.dart';
|
||||
import '../../../data/res/font_style.dart';
|
||||
import '../../../data/res/ui.dart';
|
||||
import '../../../locator.dart';
|
||||
import '../../widget/input_decoration.dart';
|
||||
|
||||
@@ -49,7 +50,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_provider.del(widget.snippet!);
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
tooltip: _s.delete,
|
||||
icon: const Icon(Icons.delete))
|
||||
@@ -93,7 +94,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
||||
} else {
|
||||
_provider.add(snippet);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../data/res/ui.dart';
|
||||
import '/core/route.dart';
|
||||
import '/data/provider/snippet.dart';
|
||||
import '/data/res/font_style.dart';
|
||||
import 'edit.dart';
|
||||
import '/view/widget/round_rect_card.dart';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -7,8 +6,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
import 'package:xterm/xterm.dart' hide TerminalColors;
|
||||
|
||||
import '../../core/utils/platform.dart';
|
||||
import '../../data/model/ssh/terminal_color.dart';
|
||||
import '../../core/utils/misc.dart';
|
||||
import '../../core/utils/ui.dart';
|
||||
@@ -17,7 +18,7 @@ import '../../data/model/server/server_private_info.dart';
|
||||
import '../../data/model/ssh/virtual_key.dart';
|
||||
import '../../data/provider/virtual_keyboard.dart';
|
||||
import '../../data/res/color.dart';
|
||||
import '../../data/res/terminal_theme.dart';
|
||||
import '../../data/res/terminal.dart';
|
||||
import '../../data/res/virtual_key.dart';
|
||||
import '../../data/store/setting.dart';
|
||||
import '../../locator.dart';
|
||||
@@ -34,6 +35,7 @@ class _SSHPageState extends State<SSHPage> {
|
||||
late final _terminal = Terminal(inputHandler: _keyboard);
|
||||
SSHClient? _client;
|
||||
final _keyboard = locator<VirtualKeyboard>();
|
||||
final _setting = locator<SettingStore>();
|
||||
late MediaQueryData _media;
|
||||
final _virtualKeyboardHeight = 57.0;
|
||||
final TerminalController _terminalController = TerminalController();
|
||||
@@ -41,14 +43,19 @@ class _SSHPageState extends State<SSHPage> {
|
||||
late TextStyle _menuTextStyle;
|
||||
late TerminalColors _termColors;
|
||||
late S _s;
|
||||
late TerminalStyle _terminalStyle;
|
||||
late TerminalUITheme _termUITheme;
|
||||
late TerminalTheme _terminalTheme;
|
||||
|
||||
var _isDark = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final termColorIdx = locator<SettingStore>().termColorIdx.fetch()!;
|
||||
final termColorIdx = _setting.termColorIdx.fetch()!;
|
||||
_termColors = TerminalColorsPlatform.values[termColorIdx].colors;
|
||||
final ts = TextStyle(fontFamily: getFileName(_setting.fontPath.fetch()));
|
||||
_terminalStyle = TerminalStyle.fromTextStyle(ts);
|
||||
initTerminal();
|
||||
}
|
||||
|
||||
@@ -59,6 +66,8 @@ class _SSHPageState extends State<SSHPage> {
|
||||
_media = MediaQuery.of(context);
|
||||
_menuTextStyle = TextStyle(color: contentColor.resolve(context));
|
||||
_s = S.of(context)!;
|
||||
_termUITheme = _isDark ? termDarkTheme : termLightTheme;
|
||||
_terminalTheme = _termUITheme.toTerminalTheme(_termColors);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -67,11 +76,30 @@ class _SSHPageState extends State<SSHPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> initTerminal() async {
|
||||
_terminal.write('Connecting...\r\n');
|
||||
void _write(String p0) {
|
||||
_terminal.write('$p0\r\n');
|
||||
}
|
||||
|
||||
_client = await genClient(widget.spi);
|
||||
_terminal.write('Connected\r\n');
|
||||
Future<void> initTerminal() async {
|
||||
_write('Connecting...\r\n');
|
||||
|
||||
_client = await genClient(
|
||||
widget.spi,
|
||||
onStatus: (p0) {
|
||||
switch (p0) {
|
||||
case GenSSHClientStatus.socket:
|
||||
_write('Destination: ${widget.spi.id}');
|
||||
return _write('Establishing socket...');
|
||||
case GenSSHClientStatus.key:
|
||||
return _write('Using private key to connect...');
|
||||
case GenSSHClientStatus.pwd:
|
||||
return _write('Sending password to auth...');
|
||||
}
|
||||
},
|
||||
);
|
||||
_write('Connected\r\n');
|
||||
_write('Terminal size: ${_terminal.viewWidth}x${_terminal.viewHeight}\r\n');
|
||||
_write('Starting shell...\r\n');
|
||||
|
||||
final session = await _client!.shell(
|
||||
pty: SSHPtyConfig(
|
||||
@@ -92,7 +120,7 @@ class _SSHPageState extends State<SSHPage> {
|
||||
|
||||
await session.done;
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,13 +133,12 @@ class _SSHPageState extends State<SSHPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final termTheme = _isDark ? termDarkTheme : termLightTheme;
|
||||
Widget child = Scaffold(
|
||||
backgroundColor: termTheme.background,
|
||||
body: _buildBody(termTheme.toTerminalTheme(_termColors)),
|
||||
bottomNavigationBar: _buildBottom(termTheme.background),
|
||||
backgroundColor: _termUITheme.background,
|
||||
body: _buildBody(),
|
||||
bottomNavigationBar: _buildBottom(),
|
||||
);
|
||||
if (Platform.isIOS) {
|
||||
if (isIOS) {
|
||||
child = AnnotatedRegion(
|
||||
value: _isDark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
|
||||
child: child,
|
||||
@@ -120,7 +147,7 @@ class _SSHPageState extends State<SSHPage> {
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildBody(TerminalTheme termTheme) {
|
||||
Widget _buildBody() {
|
||||
return SizedBox(
|
||||
height: _media.size.height -
|
||||
_virtualKeyboardHeight -
|
||||
@@ -130,8 +157,9 @@ class _SSHPageState extends State<SSHPage> {
|
||||
_terminal,
|
||||
controller: _terminalController,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
theme: termTheme,
|
||||
deleteDetection: Platform.isIOS,
|
||||
textStyle: _terminalStyle,
|
||||
theme: _terminalTheme,
|
||||
deleteDetection: isIOS,
|
||||
onTapUp: _onTapUp,
|
||||
autoFocus: true,
|
||||
keyboardAppearance: _isDark ? Brightness.dark : Brightness.light,
|
||||
@@ -139,14 +167,14 @@ class _SSHPageState extends State<SSHPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottom(Color bgColor) {
|
||||
Widget _buildBottom() {
|
||||
return SafeArea(
|
||||
child: AnimatedPadding(
|
||||
padding: _media.viewInsets,
|
||||
duration: const Duration(milliseconds: 23),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
child: Container(
|
||||
color: bgColor,
|
||||
color: _termUITheme.background,
|
||||
height: _virtualKeyboardHeight,
|
||||
child: Consumer<VirtualKeyboard>(
|
||||
builder: (_, __, ___) => _buildVirtualKey(),
|
||||
@@ -196,7 +224,7 @@ class _SSHPageState extends State<SSHPage> {
|
||||
item.text,
|
||||
style: TextStyle(
|
||||
color: selected ? primaryColor : null,
|
||||
fontSize: 17,
|
||||
fontSize: 15,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -252,6 +280,12 @@ class _SSHPageState extends State<SSHPage> {
|
||||
case VirtualKeyFunc.copy:
|
||||
copy(terminalSelected);
|
||||
break;
|
||||
case VirtualKeyFunc.snippet:
|
||||
showSnippetDialog(context, _s, (s) {
|
||||
_terminal.textInput(s.script);
|
||||
_terminal.keyInput(TerminalKey.enter);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,58 +306,56 @@ class _SSHPageState extends State<SSHPage> {
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details, CellOffset offset) {
|
||||
{
|
||||
if (_menuController.isShown) {
|
||||
_menuController.remove();
|
||||
return;
|
||||
}
|
||||
final selected = terminalSelected;
|
||||
if (selected.trim().isEmpty) {
|
||||
// _menuController.show(
|
||||
// context: context,
|
||||
// contextMenuBuilder: (context) {
|
||||
// return TextSelectionToolbar(
|
||||
// anchorAbove: detail.globalPosition,
|
||||
// anchorBelow: detail.globalPosition,
|
||||
// children: [
|
||||
// TextButton(
|
||||
// child: Text(
|
||||
// 'Paste',
|
||||
// style: _menuTextStyle,
|
||||
// ),
|
||||
// onPressed: () async {
|
||||
// _paste();
|
||||
// _menuController.remove();
|
||||
// },
|
||||
// )
|
||||
// ],
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
return;
|
||||
}
|
||||
_menuController.show(
|
||||
context: context,
|
||||
contextMenuBuilder: (context) {
|
||||
return TextSelectionToolbar(
|
||||
anchorAbove: details.globalPosition,
|
||||
anchorBelow: details.globalPosition,
|
||||
children: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
_s.copy,
|
||||
style: _menuTextStyle,
|
||||
),
|
||||
onPressed: () {
|
||||
_terminalController.setSelection(null);
|
||||
copy(selected);
|
||||
_menuController.remove();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (_menuController.isShown) {
|
||||
_menuController.remove();
|
||||
return;
|
||||
}
|
||||
final selected = terminalSelected;
|
||||
if (selected.trim().isEmpty) {
|
||||
// _menuController.show(
|
||||
// context: context,
|
||||
// contextMenuBuilder: (context) {
|
||||
// return TextSelectionToolbar(
|
||||
// anchorAbove: details.globalPosition,
|
||||
// anchorBelow: details.globalPosition,
|
||||
// children: [
|
||||
// TextButton(
|
||||
// child: Text(
|
||||
// _s.paste,
|
||||
// style: _menuTextStyle,
|
||||
// ),
|
||||
// onPressed: () async {
|
||||
// _paste();
|
||||
// _menuController.remove();
|
||||
// },
|
||||
// )
|
||||
// ],
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
return;
|
||||
}
|
||||
_menuController.show(
|
||||
context: context,
|
||||
contextMenuBuilder: (context) {
|
||||
return TextSelectionToolbar(
|
||||
anchorAbove: details.globalPosition,
|
||||
anchorBelow: details.globalPosition,
|
||||
children: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
_s.copy,
|
||||
style: _menuTextStyle,
|
||||
),
|
||||
onPressed: () {
|
||||
_terminalController.setSelection(null);
|
||||
copy(selected);
|
||||
_menuController.remove();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
47
lib/view/widget/future_widget.dart
Normal file
47
lib/view/widget/future_widget.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FutureWidget<T> extends StatelessWidget {
|
||||
final Future future;
|
||||
final Widget loading;
|
||||
final Widget Function(Object? error, StackTrace? trace) error;
|
||||
final Widget Function(T data) success;
|
||||
final Widget noData;
|
||||
final Widget Function(AsyncSnapshot<Object?> snapshot)? active;
|
||||
|
||||
const FutureWidget({
|
||||
super.key,
|
||||
required this.future,
|
||||
required this.loading,
|
||||
required this.error,
|
||||
required this.success,
|
||||
required this.noData,
|
||||
this.active,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return error(snapshot.error, snapshot.stackTrace);
|
||||
}
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
return loading;
|
||||
case ConnectionState.active:
|
||||
if (active != null) {
|
||||
return active!(snapshot);
|
||||
}
|
||||
return loading;
|
||||
case ConnectionState.done:
|
||||
if (snapshot.hasData) {
|
||||
return success(snapshot.data as T);
|
||||
}
|
||||
return noData;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,11 @@ Widget buildPicker(List<Widget> items, Function(int idx) onSelected) {
|
||||
onSelectedItemChanged: (idx) => onSelected(idx),
|
||||
physics: const FixedExtentScrollPhysics(),
|
||||
childDelegate: ListWheelChildBuilderDelegate(
|
||||
builder: (context, index) => Center(
|
||||
child: items[index],
|
||||
),
|
||||
childCount: items.length),
|
||||
builder: (context, index) => Center(
|
||||
child: items[index],
|
||||
),
|
||||
childCount: items.length,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
32
lib/view/widget/rebuild.dart
Normal file
32
lib/view/widget/rebuild.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RebuildWidget extends StatefulWidget {
|
||||
const RebuildWidget({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
static void restartApp(BuildContext context) {
|
||||
context.findAncestorStateOfType<_RebuildWidgetState>()?.restartApp();
|
||||
}
|
||||
|
||||
@override
|
||||
_RebuildWidgetState createState() => _RebuildWidgetState();
|
||||
}
|
||||
|
||||
class _RebuildWidgetState extends State<RebuildWidget> {
|
||||
Key key = UniqueKey();
|
||||
|
||||
void restartApp() {
|
||||
setState(() {
|
||||
key = UniqueKey();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeyedSubtree(
|
||||
key: key,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
31
make.dart
31
make.dart
@@ -5,17 +5,15 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
const appName = 'ServerBox';
|
||||
|
||||
const buildDataFilePath = 'lib/data/res/build_data.dart';
|
||||
|
||||
const apkPath = 'build/app/outputs/flutter-apk/app-release.apk';
|
||||
|
||||
const xcarchivePath = 'build/ios/archive/Runner.xcarchive';
|
||||
const appleXCConfigPath = '/Runner.xcodeproj/project.pbxproj';
|
||||
const appleXCConfigPath = 'Runner.xcodeproj/project.pbxproj';
|
||||
|
||||
var regAppleProjectVer = RegExp(r'CURRENT_PROJECT_VERSION = .+;');
|
||||
var regAppleMarketVer = RegExp(r'MARKETING_VERSION = .+');
|
||||
|
||||
const skslFileSuffix = '.sksl.json';
|
||||
|
||||
const buildFuncs = {
|
||||
'ios': flutterBuildIOS,
|
||||
'android': flutterBuildAndroid,
|
||||
@@ -62,7 +60,8 @@ Future<int> getGitModificationCount() async {
|
||||
|
||||
Future<String> getFlutterVersion() async {
|
||||
final result = await fvmRun(['flutter', '--version']);
|
||||
return (result.stdout as String);
|
||||
final stdout = result.stdout as String;
|
||||
return stdout.split('\n')[0].split('•')[0].split(' ')[1].trim();
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getBuildData() async {
|
||||
@@ -109,8 +108,7 @@ Future<void> flutterBuild(
|
||||
'build',
|
||||
buildType,
|
||||
];
|
||||
// No sksl cache for macos
|
||||
final skslPath = '$buildType$skslFileSuffix';
|
||||
final skslPath = '$buildType.sksl.json';
|
||||
if (await File(skslPath).exists()) {
|
||||
args.add('--bundle-sksl-path=$skslPath');
|
||||
}
|
||||
@@ -159,11 +157,12 @@ Future<void> flutterBuildMacOS() async {
|
||||
|
||||
Future<void> flutterBuildAndroid() async {
|
||||
await flutterBuild(apkPath, './release/${appName}_build_Arm64.apk', 'apk');
|
||||
await killJava();
|
||||
}
|
||||
|
||||
Future<void> changeAppleVersion() async {
|
||||
for (final path in ['ios', 'macos']) {
|
||||
final file = File(path + appleXCConfigPath);
|
||||
final file = File('$path/$appleXCConfigPath');
|
||||
final contents = await file.readAsString();
|
||||
final newContents = contents
|
||||
.replaceAll(regAppleMarketVer, 'MARKETING_VERSION = 1.0.$build;')
|
||||
@@ -172,6 +171,18 @@ Future<void> changeAppleVersion() async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> killJava() async {
|
||||
final result = await Process.run('ps', ['-A']);
|
||||
final lines = (result.stdout as String).split('\n');
|
||||
for (final line in lines) {
|
||||
if (line.contains('java')) {
|
||||
final pid = line.split(' ')[0];
|
||||
print('Killing java process: $pid');
|
||||
await Process.run('kill', [pid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main(List<String> args) async {
|
||||
if (args.isEmpty) {
|
||||
print('No action. Exit.');
|
||||
@@ -185,6 +196,8 @@ void main(List<String> args) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
await dartFormat();
|
||||
await getGitCommitCount();
|
||||
// always change version to avoid dismatch version between different
|
||||
// platforms
|
||||
await changeAppleVersion();
|
||||
await updateBuildData();
|
||||
if (args.length > 1) {
|
||||
|
||||
16
pubspec.lock
16
pubspec.lock
@@ -230,10 +230,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dartssh2
|
||||
sha256: "4c44ad7bb6a12cd56f775fc72b64e81fcc3f7971dc731c48359f2aa551d6f6a7"
|
||||
sha256: "53a230c7dd6f487b704ceef1b29323ad64d19be89e786ccbc81e157a70417a56"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.3"
|
||||
version: "2.8.2"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -646,6 +646,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
plain_notification_token:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: plain_notification_token
|
||||
sha256: "32481c1c6ad76613ed38822ffe819ea5d5534f9e07e8ff1d44aafe069f45ea24"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.4"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -738,10 +746,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "8c6892037b1824e2d7e8f59d54b3105932899008642e6372e5079c6939b4b625"
|
||||
sha256: "692261968a494e47323dcc8bc66d8d52e81bc27cb4b808e4e8d7e8079d4cc01a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.3.2"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
14
pubspec.yaml
14
pubspec.yaml
@@ -39,7 +39,7 @@ dependencies:
|
||||
extended_image: ^6.0.3
|
||||
url_launcher: ^6.1.8
|
||||
countly_flutter: ^22.2.0
|
||||
dartssh2: ^2.7.1
|
||||
dartssh2: ^2.8.2
|
||||
logging: ^1.0.2
|
||||
flutter_material_color_picker: ^1.1.0+2
|
||||
circle_chart:
|
||||
@@ -47,16 +47,22 @@ dependencies:
|
||||
url: https://github.com/lollipopkit/circle_chart
|
||||
ref: main
|
||||
# path: ../circle_chart
|
||||
r_upgrade: ^0.3.6
|
||||
|
||||
# TODO: Update this :)
|
||||
r_upgrade: 0.3.8+2
|
||||
path_provider: ^2.0.9
|
||||
easy_isolate: ^1.3.0
|
||||
share_plus: ^6.3.1
|
||||
share_plus: ^6.3.2
|
||||
intl: ^0.17.0
|
||||
share_plus_web: ^3.1.0
|
||||
# xterm: ^3.4.1
|
||||
xterm:
|
||||
xterm:
|
||||
path: ../xterm.dart
|
||||
# git:
|
||||
# ref: master
|
||||
# url: https://github.com/lollipopkit/xterm.dart
|
||||
file_picker: ^5.2.5
|
||||
plain_notification_token: ^0.0.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_native_splash: ^2.1.6
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 392 KiB |
Reference in New Issue
Block a user