diff --git a/lib/data/model/app/menu_item.dart b/lib/data/model/app/menu_item.dart index 7e2ba4b4..40f2c7e0 100644 --- a/lib/data/model/app/menu_item.dart +++ b/lib/data/model/app/menu_item.dart @@ -24,12 +24,11 @@ class DropdownBtnItem { } class ServerTabMenuItems { - static const List firstItems = [sftp, snippet, pkg, docker]; + static const List firstItems = [sftp, pkg, docker]; static const List secondItems = [edit]; static const sftp = DropdownBtnItem(text: 'SFTP', icon: Icons.insert_drive_file); - static const snippet = DropdownBtnItem(text: 'Snippet', icon: Icons.label); static const pkg = DropdownBtnItem(text: 'Pkg', icon: Icons.system_security_update); static const docker = diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index ce703461..360ecd0d 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -301,12 +301,13 @@ class ServerProvider extends BusyProvider { info.status.memory = mem; } - Future runSnippet(ServerPrivateInfo spi, Snippet snippet) async { - return await _servers - .firstWhere((element) => element.info == spi) - .client! - .run(snippet.script) - .string; + Future runSnippet(String id, Snippet snippet) async { + final client = + _servers.firstWhere((element) => element.info.id == id).client; + if (client == null) { + return null; + } + return await client.run(snippet.script).string; } } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 8a1fe28a..655dd1a5 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -61,6 +61,7 @@ class MessageLookup extends MessageLookupByLibrary { "aboutThanks": MessageLookupByLibrary.simpleMessage( "\nAll rights reserved.\n\nThanks to the following people who participated in the test."), "addAServer": MessageLookupByLibrary.simpleMessage("add a server"), + "addOne": MessageLookupByLibrary.simpleMessage("Add one"), "addPrivateKey": MessageLookupByLibrary.simpleMessage("Add private key"), "alreadyLastDir": diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart index 8e0a023f..9ed61540 100644 --- a/lib/generated/intl/messages_zh.dart +++ b/lib/generated/intl/messages_zh.dart @@ -61,6 +61,7 @@ class MessageLookup extends MessageLookupByLibrary { "aboutThanks": MessageLookupByLibrary.simpleMessage("\n保留所有权利。\n\n感谢以下参与软件测试的各位。"), "addAServer": MessageLookupByLibrary.simpleMessage("添加服务器"), + "addOne": MessageLookupByLibrary.simpleMessage("前去新增"), "addPrivateKey": MessageLookupByLibrary.simpleMessage("添加一个私钥"), "alreadyLastDir": MessageLookupByLibrary.simpleMessage("已经是最上层目录了"), "appPrimaryColor": MessageLookupByLibrary.simpleMessage("App主要色"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 6f248fa0..e8c54228 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1520,6 +1520,16 @@ class S { args: [], ); } + + /// `Add one` + String get addOne { + return Intl.message( + 'Add one', + name: 'addOne', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0f625364..d2b6215f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -145,5 +145,6 @@ "path": "Path", "goto": "Go to", "showDistLogo": "Show distribution logo", - "onServerDetailPage": "On server detail page" + "onServerDetailPage": "On server detail page", + "addOne": "Add one" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 0933b961..d4579c0d 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -145,5 +145,6 @@ "path": "路径", "goto": "前往", "showDistLogo": "显示发行版 Logo", - "onServerDetailPage": "在服务器详情页" + "onServerDetailPage": "在服务器详情页", + "addOne": "前去新增" } \ No newline at end of file diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index b45664b6..d1058019 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -10,7 +10,9 @@ import 'package:toolbox/data/model/server/server.dart'; import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:toolbox/data/model/server/server_status.dart'; import 'package:toolbox/data/provider/server.dart'; +import 'package:toolbox/data/provider/snippet.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/font_style.dart'; import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/pkg.dart'; @@ -18,7 +20,8 @@ import 'package:toolbox/view/page/docker.dart'; import 'package:toolbox/view/page/server/detail.dart'; import 'package:toolbox/view/page/server/edit.dart'; import 'package:toolbox/view/page/sftp/view.dart'; -import 'package:toolbox/view/page/snippet/list.dart'; +import 'package:toolbox/view/page/snippet/edit.dart'; +import 'package:toolbox/view/widget/picker.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; class ServerPage extends StatefulWidget { @@ -157,6 +160,8 @@ class _ServerPageState extends State context, _s.error, Text(ss.failedInfo ?? ''), []), child: Text(_s.clickSee, style: style)) : Text(topRightStr, style: style, textScaleFactor: 1.0), + const SizedBox(width: 7), + _buildSnippetBtn(spi), _buildMoreBtn(spi), ], ) @@ -191,6 +196,63 @@ class _ServerPageState extends State ); } + Widget _buildSnippetBtn(ServerPrivateInfo spi) { + return GestureDetector( + child: const Icon(Icons.play_arrow), + onTap: () { + final provider = locator(); + if (provider.snippets.isEmpty) { + showRoundDialog( + context, + _s.attention, + Text(_s.noSavedSnippet), + [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(_s.ok), + ), + TextButton( + onPressed: () => + AppRoute(const SnippetEditPage(), 'edit snippet') + .go(context), + child: Text(_s.addOne), + ) + ], + ); + return; + } + + var snippet = provider.snippets.first; + showRoundDialog( + context, + _s.choose, + buildPicker(provider.snippets.map((e) => Text(e.name)).toList(), + (idx) => snippet = provider.snippets[idx]), + [ + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + final result = + await locator().runSnippet(spi.id, snippet); + showRoundDialog( + context, + _s.result, + Text(result ?? _s.error, style: textSize13), + [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(_s.ok)) + ], + ); + }, + child: Text(_s.run), + ) + ], + ); + }, + ); + } + Widget _buildMoreBtn(ServerPrivateInfo spi) { return buildPopuopMenu( items: [ @@ -217,9 +279,6 @@ class _ServerPageState extends State case ServerTabMenuItems.sftp: AppRoute(SFTPPage(spi), 'SFTP').go(context); break; - case ServerTabMenuItems.snippet: - AppRoute(SnippetListPage(spi: spi), 'snippet list').go(context); - break; case ServerTabMenuItems.edit: AppRoute(ServerEditPage(spi: spi), 'Edit server info').go(context); break; diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index 950e1416..25c14c11 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -23,7 +23,7 @@ class SettingPage extends StatefulWidget { } class _SettingPageState extends State { - late SettingStore _setting; + late final SettingStore _setting; late int _selectedColorValue; late int _launchPageIdx; late Color priColor; @@ -32,7 +32,7 @@ class _SettingPageState extends State { late ThemeData _theme; late S _s; - var _intervalValue = 0.0; + var _updateInterval = 5.0; @override void didChangeDependencies() { @@ -49,7 +49,7 @@ class _SettingPageState extends State { _serverProvider = locator(); _setting = locator(); _launchPageIdx = _setting.launchPage.fetch()!; - _intervalValue = _setting.serverStatusUpdateInterval.fetch()!.toDouble(); + _updateInterval = _setting.serverStatusUpdateInterval.fetch()!.toDouble(); } @override @@ -126,30 +126,30 @@ class _SettingPageState extends State { _s.willTakEeffectImmediately, style: textSize13Grey, ), - trailing: Text('${_intervalValue.toInt()} ${_s.second}'), + trailing: Text('${_updateInterval.toInt()} ${_s.second}'), children: [ Slider( thumbColor: priColor, activeColor: priColor.withOpacity(0.7), min: 0, max: 10, - value: _intervalValue, + value: _updateInterval, onChanged: (newValue) { setState(() { - _intervalValue = newValue; + _updateInterval = newValue; }); }, onChangeEnd: (val) { _setting.serverStatusUpdateInterval.put(val.toInt()); _serverProvider.startAutoRefresh(); }, - label: '${_intervalValue.toInt()} ${_s.second}', + label: '${_updateInterval.toInt()} ${_s.second}', divisions: 10, ), const SizedBox( height: 3, ), - _intervalValue == 0.0 + _updateInterval == 0.0 ? Text( _s.updateIntervalEqual0, style: const TextStyle(color: Colors.grey, fontSize: 12), diff --git a/lib/view/page/snippet/list.dart b/lib/view/page/snippet/list.dart index e88f4b0e..f41aba1c 100644 --- a/lib/view/page/snippet/list.dart +++ b/lib/view/page/snippet/list.dart @@ -12,6 +12,7 @@ import 'package:toolbox/data/res/padding.dart'; import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/snippet/edit.dart'; +import 'package:toolbox/view/widget/picker.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; class SnippetListPage extends StatefulWidget { @@ -23,7 +24,7 @@ class SnippetListPage extends StatefulWidget { } class _SnippetListPageState extends State { - late ServerPrivateInfo _selectedIndex; + late ServerPrivateInfo _selectedSpi; final _textStyle = TextStyle(color: primaryColor); @@ -121,48 +122,23 @@ class _SnippetListPageState extends State { 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), - ) - ], - ), - ); + _selectedSpi = provider.servers.first.info; + return buildPicker( + provider.servers + .map((e) => Text( + e.info.name, + textAlign: TextAlign.center, + )) + .toList(), + (idx) => _selectedSpi = provider.servers[idx].info); }, ), [ TextButton( - onPressed: () async => run(context, snippet), + onPressed: () async { + Navigator.of(context).pop(); + run(context, snippet); + }, child: Text(_s.run), ), TextButton( @@ -174,8 +150,8 @@ class _SnippetListPageState extends State { } Future run(BuildContext context, Snippet snippet) async { - final result = await locator() - .runSnippet(widget.spi ?? _selectedIndex, snippet); + final id = (widget.spi ?? _selectedSpi).id; + final result = await locator().runSnippet(id, snippet); if (result != null) { showRoundDialog( context, diff --git a/lib/view/widget/picker.dart b/lib/view/widget/picker.dart new file mode 100644 index 00000000..b83e81be --- /dev/null +++ b/lib/view/widget/picker.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +Widget buildPicker(List items, Function(int idx) onSelected) { + 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) => onSelected(idx), + physics: const FixedExtentScrollPhysics(), + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) => Center( + child: items[index], + ), + childCount: items.length), + ) + ], + ), + ); +}