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

View File

@@ -61,21 +61,26 @@ class _AptManagePageState extends State<AptManagePage>
_aptProvider.init( _aptProvider.init(
si.client!, si.client!,
si.status.sysVer.dist, si.status.sysVer.dist,
() => () => scrollController.jumpTo(scrollController.position.maxScrollExtent),
scrollController.jumpTo(scrollController.position.maxScrollExtent),
() => scrollControllerUpdate () => scrollControllerUpdate
.jumpTo(scrollController.position.maxScrollExtent), .jumpTo(scrollController.position.maxScrollExtent),
onPwdRequest, onPwdRequest,
widget.spi.user); widget.spi.user,
);
_aptProvider.refreshInstalled(); _aptProvider.refreshInstalled();
} }
void onSubmitted() { void onSubmitted() {
if (textController.text == '') { if (textController.text == '') {
showRoundDialog(context, s.attention, Text(s.fieldMustNotEmpty), [ showRoundDialog(
context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.ok)), onPressed: () => Navigator.of(context).pop(), child: Text(s.ok)),
]); ],
);
return; return;
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
@@ -108,7 +113,8 @@ class _AptManagePageState extends State<AptManagePage>
s.ok, s.ok,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
)), )),
]); ],
);
return textController.text.trim(); return textController.text.trim();
} }
@@ -142,7 +148,8 @@ class _AptManagePageState extends State<AptManagePage>
child: Text( child: Text(
apt.error!, apt.error!,
textAlign: TextAlign.center, textAlign: TextAlign.center,
)), ),
),
), ),
), ),
), ),
@@ -154,9 +161,8 @@ class _AptManagePageState extends State<AptManagePage>
} }
if (apt.updateLog != null && apt.upgradeable == null) { if (apt.updateLog != null && apt.upgradeable == null) {
return SizedBox( return SizedBox(
height: _media.size.height - height:
_media.padding.top - _media.size.height - _media.padding.top - _media.padding.bottom,
_media.padding.bottom,
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints.expand(), constraints: const BoxConstraints.expand(),
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -164,7 +170,8 @@ class _AptManagePageState extends State<AptManagePage>
controller: scrollControllerUpdate, controller: scrollControllerUpdate,
child: Text(apt.updateLog!), child: Text(apt.updateLog!),
), ),
)); ),
);
} }
return ListView( return ListView(
padding: const EdgeInsets.all(13), padding: const EdgeInsets.all(13),
@@ -219,7 +226,8 @@ class _AptManagePageState extends State<AptManagePage>
controller: scrollController, controller: scrollController,
children: apt.upgradeable! children: apt.upgradeable!
.map((e) => _buildUpdateItem(e, apt)) .map((e) => _buildUpdateItem(e, apt))
.toList()), .toList(),
),
) )
] ]
: [ : [
@@ -232,7 +240,8 @@ class _AptManagePageState extends State<AptManagePage>
controller: scrollController, controller: scrollController,
child: Text(apt.upgradeLog!), child: Text(apt.upgradeLog!),
), ),
)) ),
)
], ],
) )
], ],

View File

@@ -54,8 +54,12 @@ class BackupPage extends StatelessWidget {
const SizedBox(height: 7), const SizedBox(height: 7),
const Divider(), const Divider(),
const SizedBox(height: 7), const SizedBox(height: 7),
_buildCard(s.backup, Icons.file_upload, media, _buildCard(
() => _showExportDialog(context, s)) s.backup,
Icons.file_upload,
media,
() => _showExportDialog(context, s),
)
], ],
)), )),
); );
@@ -91,7 +95,9 @@ class BackupPage extends StatelessWidget {
Future<void> _showExportDialog(BuildContext context, S s) async { Future<void> _showExportDialog(BuildContext context, S s) async {
final exportFieldController = TextEditingController() final exportFieldController = TextEditingController()
..text = _diyEncrtpt(json.encode(Backup( ..text = _diyEncrtpt(
json.encode(
Backup(
backupFormatVersion, backupFormatVersion,
DateTime.now().toString().split('.').first, DateTime.now().toString().split('.').first,
server.fetch(), server.fetch(),
@@ -100,7 +106,9 @@ class BackupPage extends StatelessWidget {
setting.primaryColor.fetch() ?? Colors.pinkAccent.value, setting.primaryColor.fetch() ?? Colors.pinkAccent.value,
setting.serverStatusUpdateInterval.fetch() ?? 2, setting.serverStatusUpdateInterval.fetch() ?? 2,
setting.launchPage.fetch() ?? 0, setting.launchPage.fetch() ?? 0,
))); ),
),
);
await showRoundDialog( await showRoundDialog(
context, context,
s.export, s.export,
@@ -115,12 +123,12 @@ class BackupPage extends StatelessWidget {
TextButton( TextButton(
child: Text(s.copy), child: Text(s.copy),
onPressed: () { onPressed: () {
Clipboard.setData( Clipboard.setData(ClipboardData(text: exportFieldController.text));
ClipboardData(text: exportFieldController.text));
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
]); ],
);
} }
Future<void> _showImportDialog(BuildContext context, S s) async { Future<void> _showImportDialog(BuildContext context, S s) async {
@@ -138,13 +146,15 @@ class BackupPage extends StatelessWidget {
[ [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)), child: Text(s.cancel),
),
TextButton( TextButton(
onPressed: () async => onPressed: () async =>
await _import(importFieldController.text.trim(), context, s), await _import(importFieldController.text.trim(), context, s),
child: const Text('GO'), child: const Text('GO'),
) )
]); ],
);
} }
Future<void> _import(String text, BuildContext context, S s) async { Future<void> _import(String text, BuildContext context, S s) async {
@@ -165,7 +175,10 @@ class BackupPage extends StatelessWidget {
} }
await showRoundDialog( await showRoundDialog(
context, s.attention, Text(s.restoreSureWithDate(backup.date)), [ context,
s.attention,
Text(s.restoreSureWithDate(backup.date)),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel), child: Text(s.cancel),
@@ -190,7 +203,8 @@ class BackupPage extends StatelessWidget {
}, },
child: Text(s.ok), child: Text(s.ok),
), ),
]); ],
);
} catch (e) { } catch (e) {
showSnackBar(context, Text(s.invalidJson)); showSnackBar(context, Text(s.invalidJson));
return; return;

View File

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

View File

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

View File

@@ -67,7 +67,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
actions: [ actions: [
IconButton( IconButton(
onPressed: () => docker.refresh(), onPressed: () => docker.refresh(),
icon: const Icon(Icons.refresh)) icon: const Icon(Icons.refresh),
)
], ],
), ),
body: _buildMain(docker), body: _buildMain(docker),
@@ -124,8 +125,13 @@ class _DockerManagePageState extends State<DockerManagePage> {
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
await _showAddCmdPreview(_buildAddCmd(imageCtrl.text.trim(), await _showAddCmdPreview(
nameCtrl.text.trim(), argsCtrl.text.trim())); _buildAddCmd(
imageCtrl.text.trim(),
nameCtrl.text.trim(),
argsCtrl.text.trim(),
),
);
}, },
child: Text(s.ok), child: Text(s.ok),
) )
@@ -208,14 +214,17 @@ class _DockerManagePageState extends State<DockerManagePage> {
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text(s.cancel)), child: Text(s.cancel),
),
TextButton( TextButton(
onPressed: () => onSubmitted(), onPressed: () => onSubmitted(),
child: Text( child: Text(
s.ok, s.ok,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
)), ),
]); ),
],
);
return textController.text.trim(); return textController.text.trim();
} }
@@ -286,8 +295,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
), ),
), ),
) )
.toList(), .toList());
);
} }
Widget _buildLoading(DockerProvider docker) { Widget _buildLoading(DockerProvider docker) {
@@ -319,7 +327,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
), ),
TextButton( TextButton(
onPressed: () => _showEditHostDialog(docker), onPressed: () => _showEditHostDialog(docker),
child: Text(s.dockerEditHost)) child: Text(s.dockerEditHost),
)
], ],
), ),
); );
@@ -343,7 +352,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
[ [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)), child: Text(s.cancel),
),
], ],
); );
} }
@@ -399,14 +409,16 @@ class _DockerManagePageState extends State<DockerManagePage> {
return ExpansionTile( return ExpansionTile(
title: Text(s.containerStatus), title: Text(s.containerStatus),
subtitle: Text(_buildSubtitle(running), style: greyTextStyle), subtitle: Text(_buildSubtitle(running), style: greyTextStyle),
children: running.map((item) { children: running.map(
(item) {
return ListTile( return ListTile(
title: Text(item.image), title: Text(item.image),
subtitle: Text(item.status), subtitle: Text(item.status),
trailing: trailing:
_buildMoreBtn(item.running, item.containerId, docker.isBusy), _buildMoreBtn(item.running, item.containerId, docker.isBusy),
); );
}).toList(), },
).toList(),
); );
} }

View File

@@ -139,7 +139,10 @@ class _MyHomePageState extends State<MyHomePage>
? Colors.white12 ? Colors.white12
: Colors.black.withOpacity(0.07) : Colors.black.withOpacity(0.07)
: Colors.transparent, : Colors.transparent,
borderRadius: const BorderRadius.all(Radius.circular(50))), borderRadius: const BorderRadius.all(
Radius.circular(50),
),
),
child: IconButton( child: IconButton(
icon: Icon(item.icon), icon: Icon(item.icon),
tooltip: tabTitleName(context, idx), tooltip: tabTitleName(context, idx),
@@ -164,12 +167,15 @@ class _MyHomePageState extends State<MyHomePage>
width: _width, width: _width,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: tabItems.map((item) { children: tabItems.map(
(item) {
int itemIndex = tabItems.indexOf(item); int itemIndex = tabItems.indexOf(item);
return _buildItem(itemIndex, item, _selectIndex == itemIndex); return _buildItem(itemIndex, item, _selectIndex == itemIndex);
}).toList(), },
).toList(),
), ),
)); ),
);
} }
Widget _buildDrawer() { Widget _buildDrawer() {
@@ -217,18 +223,25 @@ class _MyHomePageState extends State<MyHomePage>
leading: const Icon(Icons.info), leading: const Icon(Icons.info),
title: Text(s.feedback), title: Text(s.feedback),
onTap: () => showRoundDialog( onTap: () => showRoundDialog(
context, s.feedback, Text(s.feedbackOnGithub), [ context,
s.feedback,
Text(s.feedbackOnGithub),
[
TextButton( TextButton(
onPressed: () => Clipboard.setData( onPressed: () => Clipboard.setData(
const ClipboardData(text: issueUrl)), const ClipboardData(text: issueUrl)),
child: Text(s.copy)), child: Text(s.copy),
),
TextButton( TextButton(
onPressed: () => openUrl(issueUrl), onPressed: () => openUrl(issueUrl),
child: Text(s.feedback)), child: Text(s.feedback),
),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.close)) child: Text(s.close),
]), )
],
),
), ),
ListTile( ListTile(
leading: const Icon(Icons.snippet_folder), leading: const Icon(Icons.snippet_folder),
@@ -274,7 +287,8 @@ class _MyHomePageState extends State<MyHomePage>
constraints: const BoxConstraints(maxHeight: 53, maxWidth: 53), constraints: const BoxConstraints(maxHeight: 53, maxWidth: 53),
child: Container( child: Container(
color: primaryColor, color: primaryColor,
)), ),
),
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 83, maxWidth: 83), constraints: const BoxConstraints(maxHeight: 83, maxWidth: 83),
child: appIcon, child: appIcon,

View File

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

View File

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

View File

@@ -58,12 +58,15 @@ class _PrivateKeyListState extends State<StoredPrivateKeysPage> {
child: Text( child: Text(
s.edit, s.edit,
style: _textStyle, style: _textStyle,
)) ),
)
], ],
), ),
)); ));
}) })
: Center(child: Text(s.noSavedPrivateKey)); : Center(
child: Text(s.noSavedPrivateKey),
);
}, },
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(

View File

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

View File

@@ -63,12 +63,17 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(s.edit, style: textSize18), actions: [ appBar: AppBar(
title: Text(s.edit, style: textSize18),
actions: [
widget.spi != null widget.spi != null
? IconButton( ? IconButton(
onPressed: () { onPressed: () {
showRoundDialog(context, s.attention, showRoundDialog(
Text(s.sureToDeleteServer(widget.spi!.name)), [ context,
s.attention,
Text(s.sureToDeleteServer(widget.spi!.name)),
[
TextButton( TextButton(
onPressed: () { onPressed: () {
_serverProvider.delServer(widget.spi!); _serverProvider.delServer(widget.spi!);
@@ -78,15 +83,20 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
child: Text( child: Text(
s.ok, s.ok,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
)), ),
),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)) child: Text(s.cancel),
]); )
],
);
}, },
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete),
)
: const SizedBox() : const SizedBox()
]), ],
),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(17), padding: const EdgeInsets.all(17),
child: Column( child: Column(
@@ -148,7 +158,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
) )
: const SizedBox(), : const SizedBox(),
usePublicKey usePublicKey
? Consumer<PrivateKeyProvider>(builder: (_, key, __) { ? Consumer<PrivateKeyProvider>(
builder: (_, key, __) {
for (var item in key.infos) { for (var item in key.infos) {
if (item.id == widget.spi?.pubKeyId) { if (item.id == widget.spi?.pubKeyId) {
_pubKeyIndex ??= key.infos.indexOf(item); _pubKeyIndex ??= key.infos.indexOf(item);
@@ -162,16 +173,19 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
trailing: _buildRadio(key.infos.indexOf(e), e)), trailing: _buildRadio(key.infos.indexOf(e), e)),
) )
.toList(); .toList();
tiles.add(ListTile( tiles.add(
ListTile(
title: Text(s.addPrivateKey), title: Text(s.addPrivateKey),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
trailing: IconButton( trailing: IconButton(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
onPressed: () => AppRoute(const PrivateKeyEditPage(), onPressed: () => AppRoute(
const PrivateKeyEditPage(),
'private key edit page') 'private key edit page')
.go(context), .go(context),
), ),
)); ),
);
return ExpansionTile( return ExpansionTile(
textColor: primaryColor, textColor: primaryColor,
iconColor: primaryColor, iconColor: primaryColor,
@@ -183,7 +197,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
), ),
children: tiles, children: tiles,
); );
}) },
)
: const SizedBox() : const SizedBox()
], ],
), ),
@@ -208,7 +223,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
child: Text(s.cancel)) child: Text(s.cancel))
], ],
barrierDismiss: false); barrierDismiss: false,
);
if (cancel ?? true) { if (cancel ?? true) {
return; return;
} }
@@ -235,7 +251,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
port: int.parse(portController.text), port: int.parse(portController.text),
user: usernameController.text, user: usernameController.text,
pwd: authorization, pwd: authorization,
pubKeyId: usePublicKey ? _keyInfo!.id : null); pubKeyId: usePublicKey ? _keyInfo!.id : null,
);
if (widget.spi == null) { if (widget.spi == null) {
_serverProvider.addServer(spi); _serverProvider.addServer(spi);

View File

@@ -55,7 +55,8 @@ class _ServerPageState extends State<ServerPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
final child = Consumer<ServerProvider>(builder: (_, pro, __) { final child = Consumer<ServerProvider>(
builder: (_, pro, __) {
if (pro.servers.isEmpty) { if (pro.servers.isEmpty) {
return Center( return Center(
child: Text( child: Text(
@@ -78,7 +79,8 @@ class _ServerPageState extends State<ServerPage>
height: 3, height: 3,
), ),
); );
}); },
);
return Scaffold( return Scaffold(
body: child, body: child,
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
@@ -104,7 +106,8 @@ class _ServerPageState extends State<ServerPage>
child: Padding( child: Padding(
padding: const EdgeInsets.all(13), padding: const EdgeInsets.all(13),
child: _buildRealServerCard( child: _buildRealServerCard(
si.status, si.info.name, si.connectionState, si.info)), si.status, si.info.name, si.connectionState, si.info),
),
onTap: () => AppRoute(ServerDetailPage('${si.info.ip}:${si.info.port}'), onTap: () => AppRoute(ServerDetailPage('${si.info.ip}:${si.info.port}'),
'server detail page') 'server detail page')
.go(context), .go(context),

View File

@@ -71,7 +71,8 @@ class _SettingPageState extends State<SettingPage> {
} }
Widget _buildCheckUpdate() { Widget _buildCheckUpdate() {
return Consumer<AppProvider>(builder: (_, app, __) { return Consumer<AppProvider>(
builder: (_, app, __) {
String display; String display;
if (app.newestBuild != null) { if (app.newestBuild != null) {
if (app.newestBuild! > BuildData.build) { if (app.newestBuild! > BuildData.build) {
@@ -90,8 +91,10 @@ class _SettingPageState extends State<SettingPage> {
style: textSize13, style: textSize13,
textAlign: TextAlign.start, textAlign: TextAlign.start,
), ),
onTap: () => doUpdate(context, force: true)); onTap: () => doUpdate(context, force: true),
}); );
},
);
} }
Widget _buildUpdateInterval() { Widget _buildUpdateInterval() {
@@ -161,10 +164,8 @@ class _SettingPageState extends State<SettingPage> {
s.appPrimaryColor, s.appPrimaryColor,
style: textSize13, style: textSize13,
), ),
children: [ children: [_buildAppColorPicker(priColor), _buildColorPickerConfirmBtn()],
_buildAppColorPicker(priColor), );
_buildColorPickerConfirmBtn()
]);
} }
Widget _buildAppColorPicker(Color selected) { Widget _buildAppColorPicker(Color selected) {
@@ -204,7 +205,8 @@ class _SettingPageState extends State<SettingPage> {
), ),
), ),
children: tabs children: tabs
.map((e) => ListTile( .map(
(e) => ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text( title: Text(
tabTitleName(context, tabs.indexOf(e)), tabTitleName(context, tabs.indexOf(e)),
@@ -213,7 +215,8 @@ class _SettingPageState extends State<SettingPage> {
color: _theme.textTheme.bodyText2!.color!.withAlpha(177)), color: _theme.textTheme.bodyText2!.color!.withAlpha(177)),
), ),
trailing: _buildRadio(tabs.indexOf(e)), trailing: _buildRadio(tabs.indexOf(e)),
)) ),
)
.toList(), .toList(),
); );
} }

View File

@@ -152,7 +152,10 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
showRoundDialog( showRoundDialog(
context, s.sureDelete(fileName), const SizedBox(), [ context,
s.sureDelete(fileName),
const SizedBox(),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)), child: Text(s.cancel)),
@@ -164,7 +167,8 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
}, },
child: Text(s.ok), child: Text(s.ok),
), ),
]); ],
);
}, },
), ),
ListTile( ListTile(
@@ -172,13 +176,15 @@ class _SFTPDownloadedPageState extends State<SFTPDownloadedPage> {
title: Text(s.open), title: Text(s.open),
onTap: () { onTap: () {
shareFiles(context, [file.absolute.path]); shareFiles(context, [file.absolute.path]);
}), },
),
], ],
), ),
[ [
TextButton( TextButton(
onPressed: (() => Navigator.of(context).pop()), onPressed: (() => Navigator.of(context).pop()),
child: Text(s.close)) child: Text(s.close))
]); ],
);
} }
} }

View File

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

View File

@@ -79,10 +79,12 @@ class _SFTPPageState extends State<SFTPPage> {
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.close)) child: Text(s.close))
])), ],
)),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
) )
]), ],
),
body: _buildFileView(), body: _buildFileView(),
); );
} }
@@ -157,7 +159,8 @@ class _SFTPPageState extends State<SFTPPage> {
}, },
), ),
), ),
onRefresh: () => listDir(path: _status.path?.path)); onRefresh: () => listDir(path: _status.path?.path),
);
} }
} }
@@ -189,16 +192,20 @@ class _SFTPPageState extends State<SFTPPage> {
), ),
[ [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(), child: Text(s.cancel))
child: Text(s.cancel)) ],
]); );
} }
void download(BuildContext context, SftpName name) { void download(BuildContext context, SftpName name) {
showRoundDialog(context, s.download, showRoundDialog(
Text('${s.dl2Local(name.filename)}\n${s.keepForeground}'), [ context,
s.download,
Text('${s.dl2Local(name.filename)}\n${s.keepForeground}'),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.cancel)), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
@@ -207,32 +214,52 @@ class _SFTPPageState extends State<SFTPPage> {
prePath + (prePath.endsWith('/') ? '' : '/') + name.filename; prePath + (prePath.endsWith('/') ? '' : '/') + name.filename;
final local = '${(await sftpDownloadDir).path}$remotePath'; final local = '${(await sftpDownloadDir).path}$remotePath';
final pubKeyId = _status.spi!.pubKeyId; final pubKeyId = _status.spi!.pubKeyId;
locator<SftpDownloadProvider>().add( locator<SftpDownloadProvider>().add(
DownloadItem(_status.spi!, remotePath, local), DownloadItem(
_status.spi!,
remotePath,
local,
),
key: pubKeyId == null key: pubKeyId == null
? null ? null
: locator<PrivateKeyStore>().get(pubKeyId).privateKey); : locator<PrivateKeyStore>().get(pubKeyId).privateKey,
);
Navigator.of(context).pop(); Navigator.of(context).pop();
showRoundDialog(context, s.goSftpDlPage, const SizedBox(), [ showRoundDialog(
context,
s.goSftpDlPage,
const SizedBox(),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)), child: Text(s.cancel),
),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
AppRoute(const SFTPDownloadingPage(), 'sftp downloading') AppRoute(const SFTPDownloadingPage(), 'sftp downloading')
.go(context); .go(context);
}, },
child: Text(s.ok)) child: Text(s.ok),
]); )
],
);
}, },
child: Text(s.download)) child: Text(s.download),
]); )
],
);
} }
void delete(BuildContext context, SftpName file) { void delete(BuildContext context, SftpName file) {
Navigator.of(context).pop(); Navigator.of(context).pop();
showRoundDialog(context, s.attention, Text(s.sureDelete(file.filename)), [ showRoundDialog(
context,
s.attention,
Text(s.sureDelete(file.filename)),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel')), child: const Text('Cancel')),
@@ -245,8 +272,10 @@ class _SFTPPageState extends State<SFTPPage> {
child: Text( child: Text(
s.delete, s.delete,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
)), ),
]); ),
],
);
} }
void mkdir(BuildContext context) { void mkdir(BuildContext context) {
@@ -264,16 +293,21 @@ class _SFTPPageState extends State<SFTPPage> {
[ [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)), child: Text(s.cancel),
),
TextButton( TextButton(
onPressed: () { onPressed: () {
if (textController.text == '') { if (textController.text == '') {
showRoundDialog( showRoundDialog(
context, s.attention, Text(s.fieldMustNotEmpty), [ context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok)), child: Text(s.ok)),
]); ],
);
return; return;
} }
_status.client! _status.client!
@@ -284,8 +318,10 @@ class _SFTPPageState extends State<SFTPPage> {
child: Text( child: Text(
s.ok, s.ok,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
)), ),
]); ),
],
);
} }
void newFile(BuildContext context) { void newFile(BuildContext context) {
@@ -303,16 +339,22 @@ class _SFTPPageState extends State<SFTPPage> {
[ [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.cancel)), child: Text(s.cancel),
),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
if (textController.text == '') { if (textController.text == '') {
showRoundDialog( showRoundDialog(
context, s.attention, Text(s.fieldMustNotEmpty), [ context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok)), child: Text(s.ok),
]); ),
],
);
return; return;
} }
(await _status.client! (await _status.client!
@@ -324,8 +366,10 @@ class _SFTPPageState extends State<SFTPPage> {
child: Text( child: Text(
s.ok, s.ok,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
)), ),
]); ),
],
);
} }
void rename(BuildContext context, SftpName file) { void rename(BuildContext context, SftpName file) {
@@ -348,23 +392,29 @@ class _SFTPPageState extends State<SFTPPage> {
onPressed: () async { onPressed: () async {
if (textController.text == '') { if (textController.text == '') {
showRoundDialog( showRoundDialog(
context, s.attention, Text(s.fieldMustNotEmpty), [ context,
s.attention,
Text(s.fieldMustNotEmpty),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(s.ok)), child: Text(s.ok),
]); ),
],
);
return; return;
} }
await _status.client! await _status.client!.rename(file.filename, textController.text);
.rename(file.filename, textController.text);
Navigator.of(context).pop(); Navigator.of(context).pop();
listDir(); listDir();
}, },
child: Text( child: Text(
s.rename, s.rename,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
)), ),
]); ),
],
);
} }
Future<void> listDir({String? path, SSHClient? client}) async { Future<void> listDir({String? path, SSHClient? client}) async {
@@ -388,10 +438,17 @@ class _SFTPPageState extends State<SFTPPage> {
}); });
} }
} catch (e) { } catch (e) {
await showRoundDialog(context, s.error, Text(e.toString()), [ await showRoundDialog(
context,
s.error,
Text(e.toString()),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.ok)) onPressed: () => Navigator.of(context).pop(),
]); child: Text(s.ok),
)
],
);
if (_status.path!.undo()) { if (_status.path!.undo()) {
await listDir(); await listDir();
} }
@@ -424,7 +481,8 @@ class _SFTPPageState extends State<SFTPPage> {
.servers .servers
.firstWhere((s) => s.info == spi) .firstWhere((s) => s.info == spi)
.client, .client,
path: '/'); path: '/',
);
}, },
); );
} }

View File

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

View File

@@ -59,7 +59,8 @@ class _SnippetListPageState extends State<SnippetListPage> {
itemCount: key.snippets.length, itemCount: key.snippets.length,
itemExtent: 57, itemExtent: 57,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
return RoundRectCard(Padding( return RoundRectCard(
Padding(
padding: roundRectCardPadding, padding: roundRectCardPadding,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@@ -69,7 +70,8 @@ class _SnippetListPageState extends State<SnippetListPage> {
key.snippets[idx].name, key.snippets[idx].name,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Row(children: [ Row(
children: [
TextButton( TextButton(
onPressed: () => AppRoute( onPressed: () => AppRoute(
SnippetEditPage( SnippetEditPage(
@@ -79,7 +81,8 @@ class _SnippetListPageState extends State<SnippetListPage> {
child: Text( child: Text(
s.edit, s.edit,
style: _textStyle, style: _textStyle,
)), ),
),
TextButton( TextButton(
onPressed: () { onPressed: () {
final snippet = key.snippets[idx]; final snippet = key.snippets[idx];
@@ -92,27 +95,37 @@ class _SnippetListPageState extends State<SnippetListPage> {
child: Text( child: Text(
s.run, s.run,
style: _textStyle, style: _textStyle,
)) ),
]) )
],
)
], ],
), ),
)); ),
}) );
: Center(child: Text(s.noSavedSnippet)); },
)
: Center(
child: Text(s.noSavedSnippet),
);
}, },
); );
} }
void _showRunDialog(Snippet snippet) { void _showRunDialog(Snippet snippet) {
showRoundDialog(context, s.chooseDestination, showRoundDialog(
Consumer<ServerProvider>(builder: (_, provider, __) { context,
s.chooseDestination,
Consumer<ServerProvider>(
builder: (_, provider, __) {
if (provider.servers.isEmpty) { if (provider.servers.isEmpty) {
return Text(s.noServerAvailable); return Text(s.noServerAvailable);
} }
_selectedIndex = provider.servers.first.info; _selectedIndex = provider.servers.first.info;
return SizedBox( return SizedBox(
height: 111, height: 111,
child: Stack(children: [ child: Stack(
children: [
Positioned( Positioned(
top: 36, top: 36,
bottom: 36, bottom: 36,
@@ -142,24 +155,39 @@ class _SnippetListPageState extends State<SnippetListPage> {
), ),
childCount: provider.servers.length), childCount: provider.servers.length),
) )
])); ],
}), [ ),
);
},
),
[
TextButton( TextButton(
onPressed: () async => run(context, snippet), child: Text(s.run)), onPressed: () async => run(context, snippet),
child: Text(s.run),
),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.cancel)), onPressed: () => Navigator.of(context).pop(),
]); child: Text(s.cancel),
),
],
);
} }
Future<void> run(BuildContext context, Snippet snippet) async { Future<void> run(BuildContext context, Snippet snippet) async {
final result = await locator<ServerProvider>() final result = await locator<ServerProvider>()
.runSnippet(widget.spi ?? _selectedIndex, snippet); .runSnippet(widget.spi ?? _selectedIndex, snippet);
if (result != null) { if (result != null) {
showRoundDialog(context, s.result, showRoundDialog(
Text(result, style: const TextStyle(fontSize: 13)), [ context,
s.result,
Text(result, style: const TextStyle(fontSize: 13)),
[
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), child: Text(s.close)) onPressed: () => Navigator.of(context).pop(),
]); child: Text(s.close),
)
],
);
} }
} }
} }