This commit is contained in:
lollipopkit
2022-12-11 15:31:12 +08:00
parent 7e01c4cbb3
commit cfd28c3009
20 changed files with 1023 additions and 810 deletions

View File

@@ -1,4 +1,5 @@
final _dockerImageReg = RegExp(r'(\S+) +(\S+) +(\S+) +(.+) +(\S+)');
class DockerImage {
final String repo;
final String tag;
@@ -44,4 +45,3 @@ class DockerImage {
);
}
}

View File

@@ -53,7 +53,7 @@ class DockerProvider extends BusyProvider {
final verRaw = await client!.run('docker version'.withLangExport).string;
if (verRaw.contains(_dockerNotFound)) {
error = DockerErr(type: DockerErrType.notInstalled);
notifyListeners();
setBusyState(false);
return;
}
@@ -65,6 +65,7 @@ class DockerProvider extends BusyProvider {
}
try {
setBusyState();
final cmd = _wrap(_dockerPS);
// run docker ps
@@ -97,7 +98,7 @@ class DockerProvider extends BusyProvider {
error = DockerErr(type: DockerErrType.unknown, message: e.toString());
rethrow;
} finally {
notifyListeners();
setBusyState(false);
}
}
@@ -145,7 +146,8 @@ class DockerProvider extends BusyProvider {
if (code != 0) {
setBusyState(false);
return DockerErr(type: DockerErrType.unknown, message: errs.join('\n').trim());
return DockerErr(
type: DockerErrType.unknown, message: errs.join('\n').trim());
}
await refresh();
setBusyState(false);

View File

@@ -59,23 +59,28 @@ class _AptManagePageState extends State<AptManagePage>
}
_aptProvider.init(
si.client!,
si.status.sysVer.dist,
() =>
scrollController.jumpTo(scrollController.position.maxScrollExtent),
() => scrollControllerUpdate
.jumpTo(scrollController.position.maxScrollExtent),
onPwdRequest,
widget.spi.user);
si.client!,
si.status.sysVer.dist,
() => scrollController.jumpTo(scrollController.position.maxScrollExtent),
() => scrollControllerUpdate
.jumpTo(scrollController.position.maxScrollExtent),
onPwdRequest,
widget.spi.user,
);
_aptProvider.refreshInstalled();
}
void onSubmitted() {
if (textController.text == '') {
showRoundDialog(context, s.attention, Text(s.fieldMustNotEmpty), [
TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.ok)),
]);
showRoundDialog(
context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.ok)),
],
);
return;
}
Navigator.of(context).pop();
@@ -84,31 +89,32 @@ class _AptManagePageState extends State<AptManagePage>
Future<String> onPwdRequest() async {
if (!mounted) return '';
await showRoundDialog(
context,
widget.spi.user,
TextField(
controller: textController,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
onSubmitted: (_) => onSubmitted(),
decoration: InputDecoration(
labelText: s.pwd,
),
context,
widget.spi.user,
TextField(
controller: textController,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
onSubmitted: (_) => onSubmitted(),
decoration: InputDecoration(
labelText: s.pwd,
),
[
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Text(s.cancel)),
TextButton(
onPressed: () => onSubmitted(),
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
)),
]);
),
[
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Text(s.cancel)),
TextButton(
onPressed: () => onSubmitted(),
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
)),
],
);
return textController.text.trim();
}
@@ -138,11 +144,12 @@ class _AptManagePageState extends State<AptManagePage>
padding: const EdgeInsets.all(17),
child: RoundRectCard(
SingleChildScrollView(
padding: const EdgeInsets.all(17),
child: Text(
apt.error!,
textAlign: TextAlign.center,
)),
padding: const EdgeInsets.all(17),
child: Text(
apt.error!,
textAlign: TextAlign.center,
),
),
),
),
),
@@ -154,17 +161,17 @@ class _AptManagePageState extends State<AptManagePage>
}
if (apt.updateLog != null && apt.upgradeable == null) {
return SizedBox(
height: _media.size.height -
_media.padding.top -
_media.padding.bottom,
child: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: SingleChildScrollView(
padding: const EdgeInsets.all(18),
controller: scrollControllerUpdate,
child: Text(apt.updateLog!),
),
));
height:
_media.size.height - _media.padding.top - _media.padding.bottom,
child: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: SingleChildScrollView(
padding: const EdgeInsets.all(18),
controller: scrollControllerUpdate,
child: Text(apt.updateLog!),
),
),
);
}
return ListView(
padding: const EdgeInsets.all(13),
@@ -216,23 +223,25 @@ class _AptManagePageState extends State<AptManagePage>
SizedBox(
height: _media.size.height * 0.73,
child: ListView(
controller: scrollController,
children: apt.upgradeable!
.map((e) => _buildUpdateItem(e, apt))
.toList()),
controller: scrollController,
children: apt.upgradeable!
.map((e) => _buildUpdateItem(e, apt))
.toList(),
),
)
]
: [
SizedBox(
height: _media.size.height * 0.7,
child: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: SingleChildScrollView(
padding: const EdgeInsets.all(18),
controller: scrollController,
child: Text(apt.upgradeLog!),
),
))
height: _media.size.height * 0.7,
child: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: SingleChildScrollView(
padding: const EdgeInsets.all(18),
controller: scrollController,
child: Text(apt.upgradeLog!),
),
),
)
],
)
],

View File

@@ -54,8 +54,12 @@ class BackupPage extends StatelessWidget {
const SizedBox(height: 7),
const Divider(),
const SizedBox(height: 7),
_buildCard(s.backup, Icons.file_upload, media,
() => _showExportDialog(context, s))
_buildCard(
s.backup,
Icons.file_upload,
media,
() => _showExportDialog(context, s),
)
],
)),
);
@@ -91,60 +95,66 @@ 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',
..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,
),
maxLines: 7,
controller: exportFieldController,
),
[
TextButton(
child: Text(s.copy),
onPressed: () {
Clipboard.setData(
ClipboardData(text: exportFieldController.text));
Navigator.pop(context);
},
),
]);
);
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,
context,
s.import,
TextField(
decoration: const InputDecoration(
labelText: 'JSON',
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton(
onPressed: () async =>
await _import(importFieldController.text.trim(), context, s),
child: const Text('GO'),
)
]);
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 {
@@ -165,32 +175,36 @@ class BackupPage extends StatelessWidget {
}
await showRoundDialog(
context, s.attention, Text(s.restoreSureWithDate(backup.date)), [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel),
),
TextButton(
onPressed: () async {
for (final s in backup.snippets) {
snippet.put(s);
}
for (final s in backup.spis) {
server.put(s);
}
for (final s in backup.keys) {
privateKey.put(s);
}
setting.primaryColor.put(backup.primaryColor);
setting.serverStatusUpdateInterval
.put(backup.serverStatusUpdateInterval);
setting.launchPage.put(backup.launchPage);
Navigator.of(context).pop();
showSnackBar(context, Text(s.restoreSuccess));
},
child: Text(s.ok),
),
]);
context,
s.attention,
Text(s.restoreSureWithDate(backup.date)),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel),
),
TextButton(
onPressed: () async {
for (final s in backup.snippets) {
snippet.put(s);
}
for (final s in backup.spis) {
server.put(s);
}
for (final s in backup.keys) {
privateKey.put(s);
}
setting.primaryColor.put(backup.primaryColor);
setting.serverStatusUpdateInterval
.put(backup.serverStatusUpdateInterval);
setting.launchPage.put(backup.launchPage);
Navigator.of(context).pop();
showSnackBar(context, Text(s.restoreSuccess));
},
child: Text(s.ok),
),
],
);
} catch (e) {
showSnackBar(context, Text(s.invalidJson));
return;

View File

@@ -45,14 +45,17 @@ class _ConvertPageState extends State<ConvertPage>
super.build(context);
return Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 7),
controller: ScrollController(),
child: Column(children: [
padding: const EdgeInsets.symmetric(horizontal: 7),
controller: ScrollController(),
child: Column(
children: [
const SizedBox(height: 13),
_buildInputTop(),
_buildTypeOption(),
_buildResult(),
])),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
try {
@@ -119,7 +122,8 @@ class _ConvertPageState extends State<ConvertPage>
),
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(primaryColor)),
foregroundColor: MaterialStateProperty.all(primaryColor),
),
child: Icon(Icons.copy, semanticLabel: s.copy),
onPressed: () => Clipboard.setData(
ClipboardData(
@@ -136,13 +140,15 @@ class _ConvertPageState extends State<ConvertPage>
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(typeOption[_typeOptionIndex],
textScaleFactor: 1.0,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: primaryColor)),
Text(
typeOption[_typeOptionIndex],
textScaleFactor: 1.0,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: primaryColor),
),
Text(
s.currentMode,
textScaleFactor: 1.0,
@@ -153,15 +159,17 @@ class _ConvertPageState extends State<ConvertPage>
),
),
children: typeOption
.map((e) => ListTile(
title: Text(
e,
style: TextStyle(
color: _theme.textTheme.bodyText2?.color?.withAlpha(177),
),
.map(
(e) => ListTile(
title: Text(
e,
style: TextStyle(
color: _theme.textTheme.bodyText2?.color?.withAlpha(177),
),
trailing: _buildRadio(typeOption.indexOf(e)),
))
),
trailing: _buildRadio(typeOption.indexOf(e)),
),
)
.toList(),
),
);

View File

@@ -13,8 +13,10 @@ class _DebugPageState extends State<DebugPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:
AppBar(title: const Text('App log'), backgroundColor: Colors.black),
appBar: AppBar(
title: const Text('App log'),
backgroundColor: Colors.black,
),
body: _buildTerminal(context),
backgroundColor: Colors.black,
);
@@ -31,12 +33,15 @@ class _DebugPageState extends State<DebugPage> {
fontWeight: FontWeight.bold,
),
child: SingleChildScrollView(
child: Consumer<DebugProvider>(builder: (_, debug, __) {
return Column(
child: Consumer<DebugProvider>(
builder: (_, debug, __) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: debug.widgets);
}),
children: debug.widgets,
);
},
),
),
),
);

View File

@@ -66,8 +66,9 @@ class _DockerManagePageState extends State<DockerManagePage> {
title: TwoLineText(up: 'Docker', down: widget.spi.name),
actions: [
IconButton(
onPressed: () => docker.refresh(),
icon: const Icon(Icons.refresh))
onPressed: () => docker.refresh(),
icon: const Icon(Icons.refresh),
)
],
),
body: _buildMain(docker),
@@ -124,8 +125,13 @@ class _DockerManagePageState extends State<DockerManagePage> {
TextButton(
onPressed: () async {
Navigator.of(context).pop();
await _showAddCmdPreview(_buildAddCmd(imageCtrl.text.trim(),
nameCtrl.text.trim(), argsCtrl.text.trim()));
await _showAddCmdPreview(
_buildAddCmd(
imageCtrl.text.trim(),
nameCtrl.text.trim(),
argsCtrl.text.trim(),
),
);
},
child: Text(s.ok),
)
@@ -191,31 +197,34 @@ class _DockerManagePageState extends State<DockerManagePage> {
Future<String> onPwdRequest() async {
if (!mounted) return '';
await showRoundDialog(
context,
widget.spi.user,
TextField(
controller: textController,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
onSubmitted: (_) => onSubmitted(),
decoration: InputDecoration(
labelText: s.pwd,
context,
widget.spi.user,
TextField(
controller: textController,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
onSubmitted: (_) => onSubmitted(),
decoration: InputDecoration(
labelText: s.pwd,
),
),
[
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Text(s.cancel),
),
TextButton(
onPressed: () => onSubmitted(),
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
),
),
[
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Text(s.cancel)),
TextButton(
onPressed: () => onSubmitted(),
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
)),
]);
],
);
return textController.text.trim();
}
@@ -264,30 +273,29 @@ class _DockerManagePageState extends State<DockerManagePage> {
return const SizedBox();
}
return ExpansionTile(
title: Text(s.imagesList),
subtitle: Text(
s.dockerImagesFmt(docker.images!.length),
style: greyTextStyle,
),
children: docker.images!
.map(
(e) => ListTile(
title: Text(e.repo),
subtitle: Text('${e.tag} - ${e.size}'),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
final result = await _docker.run('docker rmi ${e.id} -f');
if (result != null) {
showSnackBar(
context, Text(getErrMsg(result) ?? s.unknownError));
}
},
title: Text(s.imagesList),
subtitle: Text(
s.dockerImagesFmt(docker.images!.length),
style: greyTextStyle,
),
children: docker.images!
.map(
(e) => ListTile(
title: Text(e.repo),
subtitle: Text('${e.tag} - ${e.size}'),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
final result = await _docker.run('docker rmi ${e.id} -f');
if (result != null) {
showSnackBar(
context, Text(getErrMsg(result) ?? s.unknownError));
}
},
),
),
),
)
.toList(),
);
)
.toList());
}
Widget _buildLoading(DockerProvider docker) {
@@ -318,8 +326,9 @@ class _DockerManagePageState extends State<DockerManagePage> {
textAlign: TextAlign.center,
),
TextButton(
onPressed: () => _showEditHostDialog(docker),
child: Text(s.dockerEditHost))
onPressed: () => _showEditHostDialog(docker),
child: Text(s.dockerEditHost),
)
],
),
);
@@ -342,8 +351,9 @@ class _DockerManagePageState extends State<DockerManagePage> {
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel),
),
],
);
}
@@ -399,14 +409,16 @@ class _DockerManagePageState extends State<DockerManagePage> {
return ExpansionTile(
title: Text(s.containerStatus),
subtitle: Text(_buildSubtitle(running), style: greyTextStyle),
children: running.map((item) {
return ListTile(
title: Text(item.image),
subtitle: Text(item.status),
trailing:
_buildMoreBtn(item.running, item.containerId, docker.isBusy),
);
}).toList(),
children: running.map(
(item) {
return ListTile(
title: Text(item.image),
subtitle: Text(item.status),
trailing:
_buildMoreBtn(item.running, item.containerId, docker.isBusy),
);
},
).toList(),
);
}

View File

@@ -134,12 +134,15 @@ class _MyHomePageState extends State<MyHomePage>
height: 50,
width: isSelected ? width : width - 17,
decoration: BoxDecoration(
color: isSelected
? isDarkMode
? Colors.white12
: Colors.black.withOpacity(0.07)
: Colors.transparent,
borderRadius: const BorderRadius.all(Radius.circular(50))),
color: isSelected
? isDarkMode
? Colors.white12
: Colors.black.withOpacity(0.07)
: Colors.transparent,
borderRadius: const BorderRadius.all(
Radius.circular(50),
),
),
child: IconButton(
icon: Icon(item.icon),
tooltip: tabTitleName(context, idx),
@@ -158,18 +161,21 @@ class _MyHomePageState extends State<MyHomePage>
Widget _buildBottom(BuildContext context) {
return SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.only(left: 8, top: 4, bottom: 4, right: 8),
width: _width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: tabItems.map((item) {
int itemIndex = tabItems.indexOf(item);
return _buildItem(itemIndex, item, _selectIndex == itemIndex);
}).toList(),
child: Container(
height: 56,
padding: const EdgeInsets.only(left: 8, top: 4, bottom: 4, right: 8),
width: _width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: tabItems.map(
(item) {
int itemIndex = tabItems.indexOf(item);
return _buildItem(itemIndex, item, _selectIndex == itemIndex);
},
).toList(),
),
),
));
);
}
Widget _buildDrawer() {
@@ -217,18 +223,25 @@ class _MyHomePageState extends State<MyHomePage>
leading: const Icon(Icons.info),
title: Text(s.feedback),
onTap: () => showRoundDialog(
context, s.feedback, Text(s.feedbackOnGithub), [
TextButton(
context,
s.feedback,
Text(s.feedbackOnGithub),
[
TextButton(
onPressed: () => Clipboard.setData(
const ClipboardData(text: issueUrl)),
child: Text(s.copy)),
TextButton(
child: Text(s.copy),
),
TextButton(
onPressed: () => openUrl(issueUrl),
child: Text(s.feedback)),
TextButton(
child: Text(s.feedback),
),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.close))
]),
child: Text(s.close),
)
],
),
),
ListTile(
leading: const Icon(Icons.snippet_folder),
@@ -271,10 +284,11 @@ class _MyHomePageState extends State<MyHomePage>
alignment: Alignment.center,
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 53, maxWidth: 53),
child: Container(
color: primaryColor,
)),
constraints: const BoxConstraints(maxHeight: 53, maxWidth: 53),
child: Container(
color: primaryColor,
),
),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 83, maxWidth: 83),
child: appIcon,

View File

@@ -51,8 +51,9 @@ class _PingPageState extends State<PingPage>
super.build(context);
return Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 7),
child: Column(children: [
padding: const EdgeInsets.symmetric(horizontal: 7),
child: Column(
children: [
const SizedBox(height: 13),
buildInput(context, _textEditingController,
hint: s.inputDomainHere,
@@ -69,7 +70,9 @@ class _PingPageState extends State<PingPage>
return _buildResultItem(result);
}),
),
])),
],
),
),
floatingActionButton: FloatingActionButton(
heroTag: 'ping fab',
onPressed: () {
@@ -87,19 +90,30 @@ class _PingPageState extends State<PingPage>
Widget _buildResultItem(PingResult result) {
final unknown = s.unknown;
final ms = s.ms;
return RoundRectCard(ListTile(
contentPadding: const EdgeInsets.symmetric(vertical: 7, horizontal: 17),
title: Text(result.serverName,
return RoundRectCard(
ListTile(
contentPadding: const EdgeInsets.symmetric(vertical: 7, horizontal: 17),
title: Text(
result.serverName,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold, color: primaryColor)),
subtitle: Text(
_buildPingSummary(result, unknown, ms),
style: summaryTextStyle,
),
trailing: Text(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
subtitle: Text(
_buildPingSummary(result, unknown, ms),
style: summaryTextStyle,
),
trailing: Text(
'${s.pingAvg}${result.statistic?.avg?.toStringAsFixed(2) ?? s.unknown} $ms',
style: TextStyle(fontSize: 14, color: primaryColor)),
));
style: TextStyle(
fontSize: 14,
color: primaryColor,
),
),
),
);
}
String _buildPingSummary(PingResult result, String unknown, String ms) {

View File

@@ -51,17 +51,20 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(s.edit, style: textSize18), actions: [
widget.info != null
? IconButton(
tooltip: s.delete,
onPressed: () {
_provider.delInfo(widget.info!);
Navigator.of(context).pop();
},
icon: const Icon(Icons.delete))
: const SizedBox()
]),
appBar: AppBar(
title: Text(s.edit, style: textSize18),
actions: [
widget.info != null
? IconButton(
tooltip: s.delete,
onPressed: () {
_provider.delInfo(widget.info!);
Navigator.of(context).pop();
},
icon: const Icon(Icons.delete))
: const SizedBox()
],
),
body: ListView(
padding: const EdgeInsets.all(13),
children: [

View File

@@ -51,19 +51,22 @@ class _PrivateKeyListState extends State<StoredPrivateKeysPage> {
textAlign: TextAlign.center,
),
TextButton(
onPressed: () => AppRoute(
PrivateKeyEditPage(info: key.infos[idx]),
'private key edit page')
.go(context),
child: Text(
s.edit,
style: _textStyle,
))
onPressed: () => AppRoute(
PrivateKeyEditPage(info: key.infos[idx]),
'private key edit page')
.go(context),
child: Text(
s.edit,
style: _textStyle,
),
)
],
),
));
})
: Center(child: Text(s.noSavedPrivateKey));
: Center(
child: Text(s.noSavedPrivateKey),
);
},
),
floatingActionButton: FloatingActionButton(

View File

@@ -37,8 +37,9 @@ class _ServerDetailPageState extends State<ServerDetailPage>
Widget build(BuildContext context) {
return Consumer<ServerProvider>(builder: (_, provider, __) {
return _buildMainPage(
provider.servers
.firstWhere((e) => '${e.info.ip}:${e.info.port}' == widget.id),
provider.servers.firstWhere(
(e) => '${e.info.ip}:${e.info.port}' == widget.id,
),
);
});
}
@@ -211,8 +212,9 @@ class _ServerDetailPageState extends State<ServerDetailPage>
_buildMemExplain(
(ss.memory.cache * mb).convertBytes, pColor.withAlpha(77)),
_buildMemExplain(
((ss.memory.total - ss.memory.used) * mb).convertBytes,
progressColor.resolve(context))
((ss.memory.total - ss.memory.used) * mb).convertBytes,
progressColor.resolve(context),
)
],
),
const SizedBox(
@@ -363,11 +365,12 @@ class _ServerDetailPageState extends State<ServerDetailPage>
textScaleFactor: 1.0),
),
SizedBox(
width: _media.size.width / 4,
child: Text(ns.speedOut(device: device),
style: textSize11,
textAlign: TextAlign.right,
textScaleFactor: 1.0))
width: _media.size.width / 4,
child: Text(ns.speedOut(device: device),
style: textSize11,
textAlign: TextAlign.right,
textScaleFactor: 1.0),
)
],
),
);

View File

@@ -63,30 +63,40 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(s.edit, style: textSize18), actions: [
widget.spi != null
? IconButton(
onPressed: () {
showRoundDialog(context, s.attention,
Text(s.sureToDeleteServer(widget.spi!.name)), [
TextButton(
onPressed: () {
_serverProvider.delServer(widget.spi!);
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
)),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel))
]);
},
icon: const Icon(Icons.delete))
: const SizedBox()
]),
appBar: AppBar(
title: Text(s.edit, style: textSize18),
actions: [
widget.spi != null
? IconButton(
onPressed: () {
showRoundDialog(
context,
s.attention,
Text(s.sureToDeleteServer(widget.spi!.name)),
[
TextButton(
onPressed: () {
_serverProvider.delServer(widget.spi!);
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
),
),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel),
)
],
);
},
icon: const Icon(Icons.delete),
)
: const SizedBox()
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(17),
child: Column(
@@ -148,42 +158,47 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
)
: const SizedBox(),
usePublicKey
? Consumer<PrivateKeyProvider>(builder: (_, key, __) {
for (var item in key.infos) {
if (item.id == widget.spi?.pubKeyId) {
_pubKeyIndex ??= key.infos.indexOf(item);
? Consumer<PrivateKeyProvider>(
builder: (_, key, __) {
for (var item in key.infos) {
if (item.id == widget.spi?.pubKeyId) {
_pubKeyIndex ??= key.infos.indexOf(item);
}
}
}
final tiles = key.infos
.map(
(e) => ListTile(
contentPadding: EdgeInsets.zero,
title: Text(e.id, textAlign: TextAlign.start),
trailing: _buildRadio(key.infos.indexOf(e), e)),
)
.toList();
tiles.add(ListTile(
title: Text(s.addPrivateKey),
contentPadding: EdgeInsets.zero,
trailing: IconButton(
icon: const Icon(Icons.add),
onPressed: () => AppRoute(const PrivateKeyEditPage(),
'private key edit page')
.go(context),
),
));
return ExpansionTile(
textColor: primaryColor,
iconColor: primaryColor,
tilePadding: EdgeInsets.zero,
childrenPadding: EdgeInsets.zero,
title: Text(
s.choosePrivateKey,
style: const TextStyle(fontSize: 14),
),
children: tiles,
);
})
final tiles = key.infos
.map(
(e) => ListTile(
contentPadding: EdgeInsets.zero,
title: Text(e.id, textAlign: TextAlign.start),
trailing: _buildRadio(key.infos.indexOf(e), e)),
)
.toList();
tiles.add(
ListTile(
title: Text(s.addPrivateKey),
contentPadding: EdgeInsets.zero,
trailing: IconButton(
icon: const Icon(Icons.add),
onPressed: () => AppRoute(
const PrivateKeyEditPage(),
'private key edit page')
.go(context),
),
),
);
return ExpansionTile(
textColor: primaryColor,
iconColor: primaryColor,
tilePadding: EdgeInsets.zero,
childrenPadding: EdgeInsets.zero,
title: Text(
s.choosePrivateKey,
style: const TextStyle(fontSize: 14),
),
children: tiles,
);
},
)
: const SizedBox()
],
),
@@ -197,18 +212,19 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
}
if (!usePublicKey && passwordController.text == '') {
final cancel = await showRoundDialog<bool>(
context,
s.attention,
Text(s.sureNoPwd),
[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(s.ok)),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(s.cancel))
],
barrierDismiss: false);
context,
s.attention,
Text(s.sureNoPwd),
[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(s.ok)),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(s.cancel))
],
barrierDismiss: false,
);
if (cancel ?? true) {
return;
}
@@ -230,12 +246,13 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
final authorization = passwordController.text;
final spi = ServerPrivateInfo(
name: nameController.text,
ip: ipController.text,
port: int.parse(portController.text),
user: usernameController.text,
pwd: authorization,
pubKeyId: usePublicKey ? _keyInfo!.id : null);
name: nameController.text,
ip: ipController.text,
port: int.parse(portController.text),
user: usernameController.text,
pwd: authorization,
pubKeyId: usePublicKey ? _keyInfo!.id : null,
);
if (widget.spi == null) {
_serverProvider.addServer(spi);

View File

@@ -55,30 +55,32 @@ class _ServerPageState extends State<ServerPage>
@override
Widget build(BuildContext context) {
super.build(context);
final child = Consumer<ServerProvider>(builder: (_, pro, __) {
if (pro.servers.isEmpty) {
return Center(
child: Text(
s.serverTabEmpty,
textAlign: TextAlign.center,
final child = Consumer<ServerProvider>(
builder: (_, pro, __) {
if (pro.servers.isEmpty) {
return Center(
child: Text(
s.serverTabEmpty,
textAlign: TextAlign.center,
),
);
}
return ListView.separated(
padding: const EdgeInsets.all(7),
controller: ScrollController(),
itemBuilder: (ctx, idx) {
if (idx == pro.servers.length) {
return SizedBox(height: _media.padding.bottom);
}
return _buildEachServerCard(pro.servers[idx]);
},
itemCount: pro.servers.length + 1,
separatorBuilder: (_, __) => const SizedBox(
height: 3,
),
);
}
return ListView.separated(
padding: const EdgeInsets.all(7),
controller: ScrollController(),
itemBuilder: (ctx, idx) {
if (idx == pro.servers.length) {
return SizedBox(height: _media.padding.bottom);
}
return _buildEachServerCard(pro.servers[idx]);
},
itemCount: pro.servers.length + 1,
separatorBuilder: (_, __) => const SizedBox(
height: 3,
),
);
});
},
);
return Scaffold(
body: child,
floatingActionButton: FloatingActionButton(
@@ -102,9 +104,10 @@ class _ServerPageState extends State<ServerPage>
'Edit server info page')
.go(context),
child: Padding(
padding: const EdgeInsets.all(13),
child: _buildRealServerCard(
si.status, si.info.name, si.connectionState, si.info)),
padding: const EdgeInsets.all(13),
child: _buildRealServerCard(
si.status, si.info.name, si.connectionState, si.info),
),
onTap: () => AppRoute(ServerDetailPage('${si.info.ip}:${si.info.port}'),
'server detail page')
.go(context),

View File

@@ -71,18 +71,19 @@ class _SettingPageState extends State<SettingPage> {
}
Widget _buildCheckUpdate() {
return Consumer<AppProvider>(builder: (_, app, __) {
String display;
if (app.newestBuild != null) {
if (app.newestBuild! > BuildData.build) {
display = s.versionHaveUpdate(app.newestBuild!);
return Consumer<AppProvider>(
builder: (_, app, __) {
String display;
if (app.newestBuild != null) {
if (app.newestBuild! > BuildData.build) {
display = s.versionHaveUpdate(app.newestBuild!);
} else {
display = s.versionUpdated(BuildData.build);
}
} else {
display = s.versionUpdated(BuildData.build);
display = s.versionUnknownUpdate(BuildData.build);
}
} else {
display = s.versionUnknownUpdate(BuildData.build);
}
return ListTile(
return ListTile(
contentPadding: roundRectCardPadding,
trailing: const Icon(Icons.keyboard_arrow_right),
title: Text(
@@ -90,8 +91,10 @@ class _SettingPageState extends State<SettingPage> {
style: textSize13,
textAlign: TextAlign.start,
),
onTap: () => doUpdate(context, force: true));
});
onTap: () => doUpdate(context, force: true),
);
},
);
}
Widget _buildUpdateInterval() {
@@ -147,24 +150,22 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildAppColorPreview() {
return ExpansionTile(
textColor: priColor,
tilePadding: roundRectCardPadding,
childrenPadding: roundRectCardPadding,
trailing: ClipOval(
child: Container(
color: priColor,
height: 27,
width: 27,
),
textColor: priColor,
tilePadding: roundRectCardPadding,
childrenPadding: roundRectCardPadding,
trailing: ClipOval(
child: Container(
color: priColor,
height: 27,
width: 27,
),
title: Text(
s.appPrimaryColor,
style: textSize13,
),
children: [
_buildAppColorPicker(priColor),
_buildColorPickerConfirmBtn()
]);
),
title: Text(
s.appPrimaryColor,
style: textSize13,
),
children: [_buildAppColorPicker(priColor), _buildColorPickerConfirmBtn()],
);
}
Widget _buildAppColorPicker(Color selected) {
@@ -204,16 +205,18 @@ class _SettingPageState extends State<SettingPage> {
),
),
children: tabs
.map((e) => ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
tabTitleName(context, tabs.indexOf(e)),
style: TextStyle(
fontSize: 14,
color: _theme.textTheme.bodyText2!.color!.withAlpha(177)),
),
trailing: _buildRadio(tabs.indexOf(e)),
))
.map(
(e) => ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
tabTitleName(context, tabs.indexOf(e)),
style: TextStyle(
fontSize: 14,
color: _theme.textTheme.bodyText2!.color!.withAlpha(177)),
),
trailing: _buildRadio(tabs.indexOf(e)),
),
)
.toList(),
);
}

View File

@@ -141,18 +141,21 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
void showFileActionDialog(FileSystemEntity file) {
final fileName = file.path.split('/').last;
showRoundDialog(
context,
s.choose,
Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.delete),
title: Text(s.delete),
onTap: () {
Navigator.of(context).pop();
showRoundDialog(
context, s.sureDelete(fileName), const SizedBox(), [
context,
s.choose,
Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.delete),
title: Text(s.delete),
onTap: () {
Navigator.of(context).pop();
showRoundDialog(
context,
s.sureDelete(fileName),
const SizedBox(),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
@@ -164,21 +167,24 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
},
child: Text(s.ok),
),
]);
},
),
ListTile(
leading: const Icon(Icons.open_in_new),
title: Text(s.open),
onTap: () {
shareFiles(context, [file.absolute.path]);
}),
],
),
[
TextButton(
onPressed: (() => Navigator.of(context).pop()),
child: Text(s.close))
]);
],
);
},
),
ListTile(
leading: const Icon(Icons.open_in_new),
title: Text(s.open),
onTap: () {
shareFiles(context, [file.absolute.path]);
},
),
],
),
[
TextButton(
onPressed: (() => Navigator.of(context).pop()),
child: Text(s.close))
],
);
}
}

View File

@@ -58,16 +58,18 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
Widget _wrapInCard(SftpDownloadStatus status, String? subtitle,
{Widget? trailing}) {
return RoundRectCard(ListTile(
title: Text(status.fileName),
subtitle: subtitle == null
? null
: Text(
subtitle,
style: grey,
),
trailing: trailing,
));
return RoundRectCard(
ListTile(
title: Text(status.fileName),
subtitle: subtitle == null
? null
: Text(
subtitle,
style: grey,
),
trailing: trailing,
),
);
}
Widget _buildItem(SftpDownloadStatus status) {
@@ -77,11 +79,14 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
switch (status.status) {
case SftpWorkerStatus.finished:
final time = status.spentTime.toString();
return _wrapInCard(status,
'${s.downloadFinished} ${s.spentTime(time == 'null' ? s.unknown : (time.substring(0, time.length - 7)))}',
trailing: IconButton(
onPressed: () => shareFiles(context, [status.item.localPath]),
icon: const Icon(Icons.open_in_new)));
return _wrapInCard(
status,
'${s.downloadFinished} ${s.spentTime(time == 'null' ? s.unknown : (time.substring(0, time.length - 7)))}',
trailing: IconButton(
onPressed: () => shareFiles(context, [status.item.localPath]),
icon: const Icon(Icons.open_in_new),
),
);
case SftpWorkerStatus.downloading:
return _wrapInCard(
status,
@@ -94,11 +99,14 @@ class _SFTPDownloadingPageState extends State<SFTPDownloadingPage> {
case SftpWorkerStatus.sshConnectted:
return _wrapInCard(status, s.sftpSSHConnected, trailing: loadingIcon);
default:
return _wrapInCard(status, s.unknown,
trailing: const Icon(
Icons.error,
size: 40,
));
return _wrapInCard(
status,
s.unknown,
trailing: const Icon(
Icons.error,
size: 40,
),
);
}
}
}

View File

@@ -55,34 +55,36 @@ class _SFTPPageState extends State<SFTPPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: TwoLineText(up: 'SFTP', down: widget.spi.name),
actions: [
IconButton(
onPressed: (() => showRoundDialog(
context,
s.choose,
Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.folder),
title: Text(s.createFolder),
onTap: () => mkdir(context)),
ListTile(
leading: const Icon(Icons.insert_drive_file),
title: Text(s.createFile),
onTap: () => newFile(context)),
],
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.close))
])),
icon: const Icon(Icons.add),
)
]),
centerTitle: true,
title: TwoLineText(up: 'SFTP', down: widget.spi.name),
actions: [
IconButton(
onPressed: (() => showRoundDialog(
context,
s.choose,
Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.folder),
title: Text(s.createFolder),
onTap: () => mkdir(context)),
ListTile(
leading: const Icon(Icons.insert_drive_file),
title: Text(s.createFile),
onTap: () => newFile(context)),
],
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.close))
],
)),
icon: const Icon(Icons.add),
)
],
),
body: _buildFileView(),
);
}
@@ -121,85 +123,90 @@ class _SFTPPageState extends State<SFTPPage> {
return centerCircleLoading;
} else {
return RefreshIndicator(
child: FadeIn(
key: Key(_status.spi!.name + _status.path!.path),
child: ListView.builder(
itemCount: _status.files!.length + 1,
controller: _scrollController,
itemBuilder: (context, index) {
if (index == 0) {
return _buildDestSelector();
}
final file = _status.files![index - 1];
final isDir = file.attr.isDirectory;
return ListTile(
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
title: Text(file.filename),
trailing: Text(
DateTime.fromMillisecondsSinceEpoch(
(file.attr.modifyTime ?? 0) * 1000)
.toString()
.replaceFirst('.000', ''),
style: const TextStyle(color: Colors.grey),
),
subtitle:
isDir ? null : Text((file.attr.size ?? 0).convertBytes),
onTap: () {
if (isDir) {
_status.path?.update(file.filename);
listDir(path: _status.path?.path);
} else {
onItemPress(context, file, true);
}
},
onLongPress: () => onItemPress(context, file, false),
);
},
),
child: FadeIn(
key: Key(_status.spi!.name + _status.path!.path),
child: ListView.builder(
itemCount: _status.files!.length + 1,
controller: _scrollController,
itemBuilder: (context, index) {
if (index == 0) {
return _buildDestSelector();
}
final file = _status.files![index - 1];
final isDir = file.attr.isDirectory;
return ListTile(
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
title: Text(file.filename),
trailing: Text(
DateTime.fromMillisecondsSinceEpoch(
(file.attr.modifyTime ?? 0) * 1000)
.toString()
.replaceFirst('.000', ''),
style: const TextStyle(color: Colors.grey),
),
subtitle:
isDir ? null : Text((file.attr.size ?? 0).convertBytes),
onTap: () {
if (isDir) {
_status.path?.update(file.filename);
listDir(path: _status.path?.path);
} else {
onItemPress(context, file, true);
}
},
onLongPress: () => onItemPress(context, file, false),
);
},
),
onRefresh: () => listDir(path: _status.path?.path));
),
onRefresh: () => listDir(path: _status.path?.path),
);
}
}
void onItemPress(BuildContext context, SftpName file, bool showDownload) {
showRoundDialog(
context,
s.choose,
Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.delete),
title: Text(s.delete),
onTap: () => delete(context, file),
),
ListTile(
leading: const Icon(Icons.edit),
title: Text(s.rename),
onTap: () => rename(context, file),
),
showDownload
? ListTile(
leading: const Icon(Icons.download),
title: Text(s.download),
onTap: () => download(context, file),
)
: const SizedBox()
],
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel))
]);
context,
s.choose,
Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.delete),
title: Text(s.delete),
onTap: () => delete(context, file),
),
ListTile(
leading: const Icon(Icons.edit),
title: Text(s.rename),
onTap: () => rename(context, file),
),
showDownload
? ListTile(
leading: const Icon(Icons.download),
title: Text(s.download),
onTap: () => download(context, file),
)
: const SizedBox()
],
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.cancel))
],
);
}
void download(BuildContext context, SftpName name) {
showRoundDialog(context, s.download,
Text('${s.dl2Local(name.filename)}\n${s.keepForeground}'), [
TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.cancel)),
TextButton(
showRoundDialog(
context,
s.download,
Text('${s.dl2Local(name.filename)}\n${s.keepForeground}'),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
final prePath = _status.path!.path;
@@ -207,36 +214,56 @@ class _SFTPPageState extends State<SFTPPage> {
prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
final local = '${(await sftpDownloadDir).path}$remotePath';
final pubKeyId = _status.spi!.pubKeyId;
locator<SftpDownloadProvider>().add(
DownloadItem(_status.spi!, remotePath, local),
key: pubKeyId == null
? null
: locator<PrivateKeyStore>().get(pubKeyId).privateKey);
DownloadItem(
_status.spi!,
remotePath,
local,
),
key: pubKeyId == null
? null
: locator<PrivateKeyStore>().get(pubKeyId).privateKey,
);
Navigator.of(context).pop();
showRoundDialog(context, s.goSftpDlPage, const SizedBox(), [
TextButton(
showRoundDialog(
context,
s.goSftpDlPage,
const SizedBox(),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton(
child: Text(s.cancel),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
AppRoute(const SFTPDownloadingPage(), 'sftp downloading')
.go(context);
},
child: Text(s.ok))
]);
child: Text(s.ok),
)
],
);
},
child: Text(s.download))
]);
child: Text(s.download),
)
],
);
}
void delete(BuildContext context, SftpName file) {
Navigator.of(context).pop();
showRoundDialog(context, s.attention, Text(s.sureDelete(file.filename)), [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel')),
TextButton(
showRoundDialog(
context,
s.attention,
Text(s.sureDelete(file.filename)),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel')),
TextButton(
onPressed: () {
_status.client!.remove(file.filename);
Navigator.of(context).pop();
@@ -245,126 +272,149 @@ class _SFTPPageState extends State<SFTPPage> {
child: Text(
s.delete,
style: const TextStyle(color: Colors.red),
)),
]);
),
),
],
);
}
void mkdir(BuildContext context) {
Navigator.of(context).pop();
final textController = TextEditingController();
showRoundDialog(
context,
s.createFolder,
TextField(
controller: textController,
decoration: InputDecoration(
labelText: s.name,
context,
s.createFolder,
TextField(
controller: textController,
decoration: InputDecoration(
labelText: s.name,
),
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel),
),
TextButton(
onPressed: () {
if (textController.text == '') {
showRoundDialog(
context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok)),
],
);
return;
}
_status.client!
.mkdir('${_status.path!.path}/${textController.text}');
Navigator.of(context).pop();
listDir();
},
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
),
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton(
onPressed: () {
if (textController.text == '') {
showRoundDialog(
context, s.attention, Text(s.fieldMustNotEmpty), [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok)),
]);
return;
}
_status.client!
.mkdir('${_status.path!.path}/${textController.text}');
Navigator.of(context).pop();
listDir();
},
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
)),
]);
],
);
}
void newFile(BuildContext context) {
Navigator.of(context).pop();
final textController = TextEditingController();
showRoundDialog(
context,
s.createFile,
TextField(
controller: textController,
decoration: InputDecoration(
labelText: s.name,
context,
s.createFile,
TextField(
controller: textController,
decoration: InputDecoration(
labelText: s.name,
),
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel),
),
TextButton(
onPressed: () async {
if (textController.text == '') {
showRoundDialog(
context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok),
),
],
);
return;
}
(await _status.client!
.open('${_status.path!.path}/${textController.text}'))
.writeBytes(Uint8List(0));
Navigator.of(context).pop();
listDir();
},
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
),
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton(
onPressed: () async {
if (textController.text == '') {
showRoundDialog(
context, s.attention, Text(s.fieldMustNotEmpty), [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok)),
]);
return;
}
(await _status.client!
.open('${_status.path!.path}/${textController.text}'))
.writeBytes(Uint8List(0));
Navigator.of(context).pop();
listDir();
},
child: Text(
s.ok,
style: const TextStyle(color: Colors.red),
)),
]);
],
);
}
void rename(BuildContext context, SftpName file) {
Navigator.of(context).pop();
final textController = TextEditingController();
showRoundDialog(
context,
s.rename,
TextField(
controller: textController,
decoration: InputDecoration(
labelText: s.name,
context,
s.rename,
TextField(
controller: textController,
decoration: InputDecoration(
labelText: s.name,
),
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton(
onPressed: () async {
if (textController.text == '') {
showRoundDialog(
context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok),
),
],
);
return;
}
await _status.client!.rename(file.filename, textController.text);
Navigator.of(context).pop();
listDir();
},
child: Text(
s.rename,
style: const TextStyle(color: Colors.red),
),
),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton(
onPressed: () async {
if (textController.text == '') {
showRoundDialog(
context, s.attention, Text(s.fieldMustNotEmpty), [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok)),
]);
return;
}
await _status.client!
.rename(file.filename, textController.text);
Navigator.of(context).pop();
listDir();
},
child: Text(
s.rename,
style: const TextStyle(color: Colors.red),
)),
]);
],
);
}
Future<void> listDir({String? path, SSHClient? client}) async {
@@ -388,10 +438,17 @@ class _SFTPPageState extends State<SFTPPage> {
});
}
} catch (e) {
await showRoundDialog(context, s.error, Text(e.toString()), [
TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.ok))
]);
await showRoundDialog(
context,
s.error,
Text(e.toString()),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok),
)
],
);
if (_status.path!.undo()) {
await listDir();
}
@@ -420,11 +477,12 @@ class _SFTPPageState extends State<SFTPPage> {
_status.selected = true;
_status.path = AbsolutePath('/');
listDir(
client: locator<ServerProvider>()
.servers
.firstWhere((s) => s.info == spi)
.client,
path: '/');
client: locator<ServerProvider>()
.servers
.firstWhere((s) => s.info == spi)
.client,
path: '/',
);
},
);
}

View File

@@ -41,17 +41,20 @@ class _SnippetEditPageState extends State<SnippetEditPage>
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(s.edit, style: textSize18), actions: [
widget.snippet != null
? IconButton(
onPressed: () {
_provider.del(widget.snippet!);
Navigator.of(context).pop();
},
tooltip: s.delete,
icon: const Icon(Icons.delete))
: const SizedBox()
]),
appBar: AppBar(
title: Text(s.edit, style: textSize18),
actions: [
widget.snippet != null
? IconButton(
onPressed: () {
_provider.del(widget.snippet!);
Navigator.of(context).pop();
},
tooltip: s.delete,
icon: const Icon(Icons.delete))
: const SizedBox()
],
),
body: ListView(
padding: const EdgeInsets.all(13),
children: [

View File

@@ -59,107 +59,135 @@ class _SnippetListPageState extends State<SnippetListPage> {
itemCount: key.snippets.length,
itemExtent: 57,
itemBuilder: (context, idx) {
return RoundRectCard(Padding(
padding: roundRectCardPadding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
key.snippets[idx].name,
textAlign: TextAlign.center,
),
Row(children: [
TextButton(
onPressed: () => AppRoute(
SnippetEditPage(
snippet: key.snippets[idx]),
'snippet edit page')
.go(context),
child: Text(
s.edit,
style: _textStyle,
)),
TextButton(
onPressed: () {
final snippet = key.snippets[idx];
if (widget.spi == null) {
_showRunDialog(snippet);
return;
}
run(context, snippet);
},
child: Text(
s.run,
style: _textStyle,
))
])
],
return RoundRectCard(
Padding(
padding: roundRectCardPadding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
key.snippets[idx].name,
textAlign: TextAlign.center,
),
Row(
children: [
TextButton(
onPressed: () => AppRoute(
SnippetEditPage(
snippet: key.snippets[idx]),
'snippet edit page')
.go(context),
child: Text(
s.edit,
style: _textStyle,
),
),
TextButton(
onPressed: () {
final snippet = key.snippets[idx];
if (widget.spi == null) {
_showRunDialog(snippet);
return;
}
run(context, snippet);
},
child: Text(
s.run,
style: _textStyle,
),
)
],
)
],
),
),
));
})
: Center(child: Text(s.noSavedSnippet));
);
},
)
: Center(
child: Text(s.noSavedSnippet),
);
},
);
}
void _showRunDialog(Snippet snippet) {
showRoundDialog(context, s.chooseDestination,
Consumer<ServerProvider>(builder: (_, provider, __) {
if (provider.servers.isEmpty) {
return Text(s.noServerAvailable);
}
_selectedIndex = provider.servers.first.info;
return SizedBox(
height: 111,
child: Stack(children: [
Positioned(
top: 36,
bottom: 36,
left: 0,
right: 0,
child: Container(
height: 37,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(7)),
color: Colors.black12,
showRoundDialog(
context,
s.chooseDestination,
Consumer<ServerProvider>(
builder: (_, provider, __) {
if (provider.servers.isEmpty) {
return Text(s.noServerAvailable);
}
_selectedIndex = provider.servers.first.info;
return SizedBox(
height: 111,
child: Stack(
children: [
Positioned(
top: 36,
bottom: 36,
left: 0,
right: 0,
child: Container(
height: 37,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(7)),
color: Colors.black12,
),
),
),
),
ListWheelScrollView.useDelegate(
itemExtent: 37,
diameterRatio: 1.2,
controller: FixedExtentScrollController(initialItem: 0),
onSelectedItemChanged: (idx) =>
_selectedIndex = provider.servers[idx].info,
physics: const FixedExtentScrollPhysics(),
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => Center(
child: Text(
provider.servers[index].info.name,
textAlign: TextAlign.center,
),
),
childCount: provider.servers.length),
)
],
),
ListWheelScrollView.useDelegate(
itemExtent: 37,
diameterRatio: 1.2,
controller: FixedExtentScrollController(initialItem: 0),
onSelectedItemChanged: (idx) =>
_selectedIndex = provider.servers[idx].info,
physics: const FixedExtentScrollPhysics(),
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => Center(
child: Text(
provider.servers[index].info.name,
textAlign: TextAlign.center,
),
),
childCount: provider.servers.length),
)
]));
}), [
TextButton(
onPressed: () async => run(context, snippet), child: Text(s.run)),
TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.cancel)),
]);
);
},
),
[
TextButton(
onPressed: () async => run(context, snippet),
child: Text(s.run),
),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel),
),
],
);
}
Future<void> run(BuildContext context, Snippet snippet) async {
final result = await locator<ServerProvider>()
.runSnippet(widget.spi ?? _selectedIndex, snippet);
if (result != null) {
showRoundDialog(context, s.result,
Text(result, style: const TextStyle(fontSize: 13)), [
TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.close))
]);
showRoundDialog(
context,
s.result,
Text(result, style: const TextStyle(fontSize: 13)),
[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(s.close),
)
],
);
}
}
}