opt.: loading dialog
This commit is contained in:
@@ -231,9 +231,9 @@ class BackupPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
try {
|
||||
context.showLoadingDialog();
|
||||
final backup =
|
||||
await Computer.shared.start(Backup.fromJsonString, text.trim());
|
||||
final backup = await context.showLoadingDialog(
|
||||
fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()),
|
||||
);
|
||||
if (backupFormatVersion != backup.version) {
|
||||
context.showSnackBar(l10n.backupVersionNotMatch);
|
||||
return;
|
||||
@@ -261,8 +261,6 @@ class BackupPage extends StatelessWidget {
|
||||
} catch (e, trace) {
|
||||
Loggers.app.warning('Import backup failed', e, trace);
|
||||
context.showSnackBar(e.toString());
|
||||
} finally {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,9 +386,10 @@ class BackupPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
try {
|
||||
context.showLoadingDialog();
|
||||
final backup =
|
||||
await Computer.shared.start(Backup.fromJsonString, text.trim());
|
||||
final backup = await context.showLoadingDialog(
|
||||
fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()),
|
||||
);
|
||||
|
||||
if (backupFormatVersion != backup.version) {
|
||||
context.showSnackBar(l10n.backupVersionNotMatch);
|
||||
return;
|
||||
@@ -418,8 +417,6 @@ class BackupPage extends StatelessWidget {
|
||||
} catch (e, trace) {
|
||||
Loggers.app.warning('Import backup failed', e, trace);
|
||||
context.showSnackBar(e.toString());
|
||||
} finally {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +72,8 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
title: TwoLineText(up: l10n.container, down: widget.spi.name),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
context.showLoadingDialog();
|
||||
await _container.refresh();
|
||||
context.pop();
|
||||
},
|
||||
onPressed: () =>
|
||||
context.showLoadingDialog(fn: () => _container.refresh()),
|
||||
icon: const Icon(Icons.refresh),
|
||||
)
|
||||
],
|
||||
@@ -393,9 +390,10 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
context.showLoadingDialog();
|
||||
final result = await _container.run(cmd);
|
||||
context.pop();
|
||||
|
||||
final result = await context.showLoadingDialog(
|
||||
fn: () => _container.run(cmd),
|
||||
);
|
||||
if (result != null) {
|
||||
context.showSnackBar(result.message ?? l10n.unknownError);
|
||||
}
|
||||
@@ -506,9 +504,10 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
context.showLoadingDialog();
|
||||
final result = await _container.delete(id, force);
|
||||
context.pop();
|
||||
|
||||
final result = await context.showLoadingDialog(
|
||||
fn: () => _container.delete(id, force),
|
||||
);
|
||||
if (result != null) {
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.error),
|
||||
@@ -522,9 +521,9 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
);
|
||||
break;
|
||||
case ContainerMenu.start:
|
||||
context.showLoadingDialog();
|
||||
final result = await _container.start(id);
|
||||
context.pop();
|
||||
final result = await context.showLoadingDialog(
|
||||
fn: () => _container.start(id),
|
||||
);
|
||||
if (result != null) {
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.error),
|
||||
@@ -533,9 +532,9 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
}
|
||||
break;
|
||||
case ContainerMenu.stop:
|
||||
context.showLoadingDialog();
|
||||
final result = await _container.stop(id);
|
||||
context.pop();
|
||||
final result = await context.showLoadingDialog(
|
||||
fn: () => _container.stop(id),
|
||||
);
|
||||
if (result != null) {
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.error),
|
||||
@@ -544,9 +543,9 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
}
|
||||
break;
|
||||
case ContainerMenu.restart:
|
||||
context.showLoadingDialog();
|
||||
final result = await _container.restart(id);
|
||||
context.pop();
|
||||
final result = await context.showLoadingDialog(
|
||||
fn: () => _container.restart(id),
|
||||
);
|
||||
if (result != null) {
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.error),
|
||||
|
||||
@@ -148,9 +148,10 @@ class _EditorPageState extends State<EditorPage> {
|
||||
// If path is not null, then it's a file editor
|
||||
// save the text and return true to pop the page
|
||||
if (widget.path != null) {
|
||||
context.showLoadingDialog();
|
||||
await File(widget.path!).writeAsString(_controller.text);
|
||||
context.pop();
|
||||
await context.showLoadingDialog(
|
||||
fn: () => File(widget.path!).writeAsString(_controller.text),
|
||||
);
|
||||
|
||||
context.pop(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,59 +108,60 @@ class _ServerEditPageState extends State<ServerEditPage> {
|
||||
PreferredSizeWidget _buildAppBar() {
|
||||
return CustomAppBar(
|
||||
title: Text(l10n.edit, style: UIs.text18),
|
||||
actions: widget.spi != null
|
||||
? [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
var delScripts = false;
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.attention),
|
||||
child: StatefulBuilder(builder: (ctx, setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.askContinue(
|
||||
'${l10n.delete} ${l10n.server}(${widget.spi!.name})',
|
||||
)),
|
||||
UIs.height13,
|
||||
if (widget.spi?.server?.canViewDetails ?? false)
|
||||
CheckboxListTile(
|
||||
value: delScripts,
|
||||
onChanged: (_) => setState(
|
||||
() => delScripts = !delScripts,
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: Text(l10n.deleteScripts),
|
||||
tileColor: Colors.transparent,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
if (delScripts) {
|
||||
context.showLoadingDialog();
|
||||
const cmd =
|
||||
'rm ${ShellFunc.srvBoxDir}/mobile_v*.sh';
|
||||
await widget.spi?.server?.client?.run(cmd);
|
||||
context.pop();
|
||||
}
|
||||
Pros.server.delServer(widget.spi!.id);
|
||||
context.pop(true);
|
||||
},
|
||||
child: Text(l10n.ok, style: UIs.textRed),
|
||||
),
|
||||
],
|
||||
actions: widget.spi != null ? [_buildDelBtn()] : null,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDelBtn() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
var delScripts = false;
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.attention),
|
||||
child: StatefulBuilder(builder: (ctx, setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.askContinue(
|
||||
'${l10n.delete} ${l10n.server}(${widget.spi!.name})',
|
||||
)),
|
||||
UIs.height13,
|
||||
if (widget.spi?.server?.canViewDetails ?? false)
|
||||
CheckboxListTile(
|
||||
value: delScripts,
|
||||
onChanged: (_) => setState(
|
||||
() => delScripts = !delScripts,
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: Text(l10n.deleteScripts),
|
||||
tileColor: Colors.transparent,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
if (delScripts) {
|
||||
await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
const cmd = 'rm ${ShellFunc.srvBoxDir}/mobile_v*.sh';
|
||||
return widget.spi?.server?.client?.run(cmd);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
}
|
||||
Pros.server.delServer(widget.spi!.id);
|
||||
context.pop(true);
|
||||
},
|
||||
child: Text(l10n.ok, style: UIs.textRed),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1161,6 +1161,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
_setting.preferTemperatureDevs.put(list);
|
||||
context.pop();
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: Text(l10n.preferTemperatureDeviceList),
|
||||
subtitle: Text(l10n.preferTemperatureDeviceListTip, style: UIs.textGrey),
|
||||
|
||||
@@ -406,9 +406,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
SftpReqType.download,
|
||||
);
|
||||
Pros.sftp.add(req, completer: completer);
|
||||
context.showLoadingDialog();
|
||||
await completer.future;
|
||||
context.pop();
|
||||
await context.showLoadingDialog(fn: () => completer.future);
|
||||
|
||||
final result = await AppRoute.editor(path: localPath).go<bool>(context);
|
||||
if (result != null && result) {
|
||||
@@ -471,19 +469,18 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
context.showLoadingDialog();
|
||||
final remotePath = _getRemotePath(file);
|
||||
try {
|
||||
if (useRmr) {
|
||||
await _client!.run('rm -r "$remotePath"');
|
||||
} else if (file.attr.isDirectory) {
|
||||
await _status.client!.rmdir(remotePath);
|
||||
} else {
|
||||
await _status.client!.remove(remotePath);
|
||||
}
|
||||
context.pop();
|
||||
await context.showLoadingDialog(fn: () async {
|
||||
final remotePath = _getRemotePath(file);
|
||||
if (useRmr) {
|
||||
await _client!.run('rm -r "$remotePath"');
|
||||
} else if (file.attr.isDirectory) {
|
||||
await _status.client!.rmdir(remotePath);
|
||||
} else {
|
||||
await _status.client!.remove(remotePath);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
context.pop();
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.error),
|
||||
child: Text(e.toString()),
|
||||
@@ -574,9 +571,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
}
|
||||
context.pop();
|
||||
final path = '${_status.path!.path}/${textController.text}';
|
||||
context.showLoadingDialog();
|
||||
await _client!.run('touch "$path"');
|
||||
context.pop();
|
||||
await context.showLoadingDialog(
|
||||
fn: () => _client!.run('touch "$path"'));
|
||||
_listDir();
|
||||
},
|
||||
child: Text(l10n.ok, style: UIs.textRed),
|
||||
@@ -640,9 +636,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
);
|
||||
return;
|
||||
}
|
||||
context.showLoadingDialog();
|
||||
await _client?.run(cmd);
|
||||
context.pop();
|
||||
await context.showLoadingDialog(fn: () async => _client?.run(cmd));
|
||||
_listDir();
|
||||
}
|
||||
|
||||
@@ -657,67 +651,65 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
|
||||
/// Only return true if the path is changed
|
||||
Future<bool> _listDir() async {
|
||||
// Allow dismiss, because may this op will take a long time
|
||||
context.showLoadingDialog(barrierDismiss: true);
|
||||
if (_status.client == null) {
|
||||
final sftpc = await _client?.sftp();
|
||||
_status.client = sftpc;
|
||||
}
|
||||
try {
|
||||
final listPath = _status.path?.path ?? '/';
|
||||
final fs = await _status.client?.listdir(listPath);
|
||||
if (fs == null) {
|
||||
return false;
|
||||
}
|
||||
fs.sort((a, b) => a.filename.compareTo(b.filename));
|
||||
return context.showLoadingDialog(
|
||||
fn: () async {
|
||||
_status.client ??= await _client?.sftp();
|
||||
try {
|
||||
final listPath = _status.path?.path ?? '/';
|
||||
final fs = await _status.client?.listdir(listPath);
|
||||
if (fs == null) {
|
||||
return false;
|
||||
}
|
||||
fs.sort((a, b) => a.filename.compareTo(b.filename));
|
||||
|
||||
/// Issue #97
|
||||
/// In order to compatible with the Synology NAS
|
||||
/// which not has '.' and '..' in listdir
|
||||
if (fs.isNotEmpty && fs.first.filename == '.') {
|
||||
fs.removeAt(0);
|
||||
}
|
||||
/// Issue #97
|
||||
/// In order to compatible with the Synology NAS
|
||||
/// which not has '.' and '..' in listdir
|
||||
if (fs.isNotEmpty && fs.first.filename == '.') {
|
||||
fs.removeAt(0);
|
||||
}
|
||||
|
||||
/// Issue #96
|
||||
/// Due to [WillPopScope] added in this page
|
||||
/// There is no need to keep '..' folder in listdir
|
||||
/// So remove it
|
||||
if (fs.isNotEmpty && fs.first.filename == '..') {
|
||||
fs.removeAt(0);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_status.files = fs;
|
||||
});
|
||||
context.pop();
|
||||
/// Issue #96
|
||||
/// Due to [WillPopScope] added in this page
|
||||
/// There is no need to keep '..' folder in listdir
|
||||
/// So remove it
|
||||
if (fs.isNotEmpty && fs.first.filename == '..') {
|
||||
fs.removeAt(0);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_status.files = fs;
|
||||
});
|
||||
|
||||
// Only update history when success
|
||||
if (Stores.setting.sftpOpenLastPath.fetch()) {
|
||||
Stores.history.sftpLastPath.put(widget.spi.id, listPath);
|
||||
// Only update history when success
|
||||
if (Stores.setting.sftpOpenLastPath.fetch()) {
|
||||
Stores.history.sftpLastPath.put(widget.spi.id, listPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e, trace) {
|
||||
Loggers.app.warning('List dir failed', e, trace);
|
||||
await _backward();
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 177),
|
||||
() => context.showRoundDialog(
|
||||
title: Text(l10n.error),
|
||||
child: Text(e.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(l10n.ok),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e, trace) {
|
||||
context.pop();
|
||||
Loggers.app.warning('List dir failed', e, trace);
|
||||
await _backward();
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 177),
|
||||
() => context.showRoundDialog(
|
||||
title: Text(l10n.error),
|
||||
child: Text(e.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(l10n.ok),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
barrierDismiss: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _backward() async {
|
||||
|
||||
@@ -223,7 +223,8 @@ bool _checkClient(BuildContext context, String id) {
|
||||
|
||||
Future<void> _onPkg(BuildContext context, ServerPrivateInfo spi) async {
|
||||
final server = spi.server;
|
||||
if (server == null) {
|
||||
final client = server?.client;
|
||||
if (server == null || client == null) {
|
||||
context.showSnackBar(l10n.noClient);
|
||||
return;
|
||||
}
|
||||
@@ -232,42 +233,43 @@ Future<void> _onPkg(BuildContext context, ServerPrivateInfo spi) async {
|
||||
context.showSnackBar(l10n.noResult);
|
||||
return;
|
||||
}
|
||||
|
||||
final pkg = PkgManager.fromDist(sys.dist);
|
||||
if (pkg == null) {
|
||||
context.showSnackBar('Unsupported dist: $sys');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update pkg list
|
||||
context.showLoadingDialog();
|
||||
final updateCmd = pkg?.update;
|
||||
if (updateCmd != null) {
|
||||
await server.client!.execWithPwd(
|
||||
updateCmd,
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
context.pop();
|
||||
await context.showLoadingDialog(
|
||||
fn: () async {
|
||||
final updateCmd = pkg.update;
|
||||
if (updateCmd != null) {
|
||||
await client.execWithPwd(updateCmd, context: context);
|
||||
}
|
||||
},
|
||||
barrierDismiss: true,
|
||||
);
|
||||
|
||||
final listCmd = pkg?.listUpdate;
|
||||
final listCmd = pkg.listUpdate;
|
||||
if (listCmd == null) {
|
||||
context.showSnackBar('Unsupported dist: $sys');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get upgrade list
|
||||
context.showLoadingDialog();
|
||||
final result = await server.client?.run(listCmd).string;
|
||||
context.pop();
|
||||
if (result == null) {
|
||||
context.showSnackBar(l10n.noResult);
|
||||
return;
|
||||
}
|
||||
final list = pkg?.updateListRemoveUnused(result.split('\n'));
|
||||
final upgradeable = list?.map((e) => UpgradePkgInfo(e, pkg)).toList();
|
||||
if (upgradeable == null || upgradeable.isEmpty) {
|
||||
final result = await context.showLoadingDialog(fn: () async {
|
||||
return await client.run(listCmd).string;
|
||||
});
|
||||
final list = pkg.updateListRemoveUnused(result.split('\n'));
|
||||
final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList();
|
||||
if (upgradeable.isEmpty) {
|
||||
context.showSnackBar(l10n.noUpdateAvailable);
|
||||
return;
|
||||
}
|
||||
final args = upgradeable.map((e) => e.package).join(' ');
|
||||
final isSU = server.spi.user == 'root';
|
||||
final upgradeCmd = isSU ? pkg?.upgrade(args) : 'sudo ${pkg?.upgrade(args)}';
|
||||
final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}';
|
||||
|
||||
// Confirm upgrade
|
||||
final gotoUpgrade = await context.showRoundDialog<bool>(
|
||||
|
||||
Reference in New Issue
Block a user