From e0fb591deaafc07bc1fd3c36db37d5a2cd4b9dca Mon Sep 17 00:00:00 2001 From: LollipopKit <2036293523@qq.com> Date: Sat, 6 Nov 2021 14:05:03 +0800 Subject: [PATCH] Simply implement snippet running. --- lib/core/analysis.dart | 11 +- lib/core/utils.dart | 6 +- lib/data/model/server/memory.dart | 8 +- lib/data/provider/server.dart | 5 + lib/data/provider/snippet.dart | 32 +++++ lib/data/res/build_data.dart | 9 +- lib/locator.dart | 2 + lib/main.dart | 3 + lib/view/page/home.dart | 5 +- lib/view/page/private_key/edit.dart | 12 +- .../private_key/{stored.dart => list.dart} | 0 lib/view/page/server/detail.dart | 16 ++- lib/view/page/server/edit.dart | 1 + lib/view/page/server/tab.dart | 10 +- lib/view/page/setting.dart | 12 +- lib/view/page/snippet/edit.dart | 82 +++++++++++- lib/view/page/snippet/list.dart | 125 +++++++++++++++++- 17 files changed, 305 insertions(+), 34 deletions(-) create mode 100644 lib/data/provider/snippet.dart rename lib/view/page/private_key/{stored.dart => list.dart} (100%) diff --git a/lib/core/analysis.dart b/lib/core/analysis.dart index ee922d71..0f9f11fc 100644 --- a/lib/core/analysis.dart +++ b/lib/core/analysis.dart @@ -6,7 +6,10 @@ class Analysis { static const _url = 'https://countly.xuty.cc'; static const _key = '80372a2a66424b32d0ac8991bfa1ef058bd36b1f'; + static bool _enabled = false; + static Future init(bool debug) async { + _enabled = true; await Countly.setLoggingEnabled(debug); await Countly.init(_url, _key); await Countly.start(); @@ -15,10 +18,14 @@ class Analysis { } static void recordView(String view) { - Countly.recordView(view); + if (_enabled) { + Countly.recordView(view); + } } static void recordException(Object exception, [bool fatal = false]) { - Countly.logException(exception.toString(), !fatal, null); + if (_enabled) { + Countly.logException(exception.toString(), !fatal, null); + } } } diff --git a/lib/core/utils.dart b/lib/core/utils.dart index e4850f00..32ff99c8 100644 --- a/lib/core/utils.dart +++ b/lib/core/utils.dart @@ -15,13 +15,13 @@ bool isDarkMode(BuildContext context) => void showSnackBar(BuildContext context, Widget child) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: child)); -void showSnackBarWithAction( - BuildContext context, String content, String action, Function onTap) { +void showSnackBarWithAction(BuildContext context, String content, String action, + GestureTapCallback onTap) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(content), action: SnackBarAction( label: action, - onPressed: () => onTap, + onPressed: onTap, ), )); } diff --git a/lib/data/model/server/memory.dart b/lib/data/model/server/memory.dart index 0a11bed7..c82720ae 100644 --- a/lib/data/model/server/memory.dart +++ b/lib/data/model/server/memory.dart @@ -5,5 +5,11 @@ class Memory { int shared; int cache; int avail; - Memory({required this.total, required this.used, required this.free, required this.shared, required this.cache, required this.avail}); + Memory( + {required this.total, + required this.used, + required this.free, + required this.shared, + required this.cache, + required this.avail}); } diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index d290b003..605e8a52 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -13,6 +13,7 @@ import 'package:toolbox/data/model/server/disk_info.dart'; 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/model/server/snippet.dart'; import 'package:toolbox/data/model/server/tcp_status.dart'; import 'package:toolbox/data/store/server.dart'; import 'package:toolbox/data/store/setting.dart'; @@ -290,4 +291,8 @@ class ServerProvider extends BusyProvider { } return emptyMemory; } + + Future runSnippet(int idx, Snippet snippet) { + return _servers[idx].client.execute(snippet.script); + } } diff --git a/lib/data/provider/snippet.dart b/lib/data/provider/snippet.dart new file mode 100644 index 00000000..580a0d4a --- /dev/null +++ b/lib/data/provider/snippet.dart @@ -0,0 +1,32 @@ +import 'package:toolbox/core/provider_base.dart'; +import 'package:toolbox/data/model/server/snippet.dart'; +import 'package:toolbox/data/store/snippet.dart'; +import 'package:toolbox/locator.dart'; + +class SnippetProvider extends BusyProvider { + List get snippets => _snippets; + late List _snippets; + + void loadData() { + _snippets = locator().fetch(); + } + + void addInfo(Snippet snippet) { + _snippets.add(snippet); + locator().put(snippet); + notifyListeners(); + } + + void delInfo(Snippet snippet) { + _snippets.removeWhere((e) => e.name == snippet.name); + locator().delete(snippet); + notifyListeners(); + } + + void updateInfo(Snippet old, Snippet newOne) { + final idx = _snippets.indexWhere((e) => e.name == old.name); + _snippets[idx] = newOne; + locator().update(old, newOne); + notifyListeners(); + } +} diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index fce0fadc..e199d42f 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,8 +2,9 @@ class BuildData { static const String name = "ToolBox"; - static const int build = 60; - static const String engine = "Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 18116933e7 (3 weeks ago) • 2021-10-15 10:46:35 -0700\nEngine • revision d3ea636dc5\nTools • Dart 2.14.4\n"; - static const String buildAt = "2021-11-02 20:36:41.010803"; - static const int modifications = 2; + static const int build = 61; + static const String engine = + "Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 18116933e7 (3 weeks ago) • 2021-10-15 10:46:35 -0700\nEngine • revision d3ea636dc5\nTools • Dart 2.14.4\n"; + static const String buildAt = "2021-11-05 12:58:33.427838"; + static const int modifications = 15; } diff --git a/lib/locator.dart b/lib/locator.dart index bcfb34ed..0b3287da 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -3,6 +3,7 @@ import 'package:toolbox/data/provider/app.dart'; import 'package:toolbox/data/provider/debug.dart'; import 'package:toolbox/data/provider/private_key.dart'; import 'package:toolbox/data/provider/server.dart'; +import 'package:toolbox/data/provider/snippet.dart'; import 'package:toolbox/data/service/app.dart'; import 'package:toolbox/data/store/private_key.dart'; import 'package:toolbox/data/store/server.dart'; @@ -19,6 +20,7 @@ void setupLocatorForProviders() { locator.registerSingleton(AppProvider()); locator.registerSingleton(DebugProvider()); locator.registerSingleton(ServerProvider()); + locator.registerSingleton(SnippetProvider()); locator.registerSingleton(PrivateKeyProvider()); } diff --git a/lib/main.dart b/lib/main.dart index 54b362d3..16ed5da3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,11 +10,13 @@ import 'package:toolbox/data/provider/app.dart'; import 'package:toolbox/data/provider/debug.dart'; import 'package:toolbox/data/provider/private_key.dart'; import 'package:toolbox/data/provider/server.dart'; +import 'package:toolbox/data/provider/snippet.dart'; import 'package:toolbox/locator.dart'; Future initApp() async { await Hive.initFlutter(); await setupLocator(); + locator().loadData(); locator().loadData(); ///设置Logger @@ -62,6 +64,7 @@ Future main() async { ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), + ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), ], child: const MyApp(), diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index b7f895f7..5f9ed47b 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -13,7 +13,7 @@ import 'package:toolbox/data/res/url.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/convert.dart'; import 'package:toolbox/view/page/debug.dart'; -import 'package:toolbox/view/page/private_key/stored.dart'; +import 'package:toolbox/view/page/private_key/list.dart'; import 'package:toolbox/view/page/server/tab.dart'; import 'package:toolbox/view/page/setting.dart'; import 'package:toolbox/view/page/snippet/list.dart'; @@ -92,8 +92,7 @@ class _MyHomePageState extends State leading: const Icon(Icons.snippet_folder), title: const Text('Snippet'), onTap: () => - AppRoute(const SnippetListPage(), 'snippet list') - .go(context), + AppRoute(const SnippetListPage(), 'snippet list').go(context), ), AboutListTile( icon: const Icon(Icons.text_snippet), diff --git a/lib/view/page/private_key/edit.dart b/lib/view/page/private_key/edit.dart index fe6f78ed..ddd7b3c0 100644 --- a/lib/view/page/private_key/edit.dart +++ b/lib/view/page/private_key/edit.dart @@ -1,5 +1,6 @@ import 'package:after_layout/after_layout.dart'; import 'package:flutter/material.dart'; +import 'package:toolbox/core/utils.dart'; import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:toolbox/data/provider/private_key.dart'; import 'package:toolbox/locator.dart'; @@ -70,8 +71,15 @@ class _PrivateKeyEditPageState extends State floatingActionButton: FloatingActionButton( child: const Icon(Icons.send), onPressed: () { - final info = PrivateKeyInfo( - nameController.text, keyController.text, pwdController.text); + final name = nameController.text; + final key = keyController.text; + final pwd = pwdController.text; + if (name.isEmpty || key.isEmpty || pwd.isEmpty) { + showSnackBar( + context, const Text('Three fields must not be empty.')); + return; + } + final info = PrivateKeyInfo(name, key, pwd); if (widget.info != null) { _provider.updateInfo(widget.info!, info); } else { diff --git a/lib/view/page/private_key/stored.dart b/lib/view/page/private_key/list.dart similarity index 100% rename from lib/view/page/private_key/stored.dart rename to lib/view/page/private_key/list.dart diff --git a/lib/view/page/server/detail.dart b/lib/view/page/server/detail.dart index 13c7f635..67d2a562 100644 --- a/lib/view/page/server/detail.dart +++ b/lib/view/page/server/detail.dart @@ -44,12 +44,16 @@ class _ServerDetailPageState extends State return Scaffold( appBar: AppBar( title: Text(si.info.name), - actions: [IconButton(onPressed: () => AppRoute( - ServerEditPage( - spi: si.info, - ), - 'Edit server info page') - .go(context), icon: const Icon(Icons.edit))], + actions: [ + IconButton( + onPressed: () => AppRoute( + ServerEditPage( + spi: si.info, + ), + 'Edit server info page') + .go(context), + icon: const Icon(Icons.edit)) + ], ), body: ListView( padding: const EdgeInsets.all(17), diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index cea7623d..2464c9f1 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -59,6 +59,7 @@ class _ServerEditPageState extends State with AfterLayoutMixin { onPressed: () { _serverProvider.delServer(widget.spi!); Navigator.of(context).pop(); + Navigator.of(context).pop(); }, child: const Text( 'Yes', diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index aa773ca8..79e8c060 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -91,11 +91,11 @@ class _ServerPageState extends State return Card( child: InkWell( onLongPress: () => AppRoute( - ServerEditPage( - spi: si.info, - ), - 'Edit server info page') - .go(context), + ServerEditPage( + spi: si.info, + ), + 'Edit server info page') + .go(context), child: Padding( padding: const EdgeInsets.all(13), child: diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index da895d5b..be34eb15 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -66,10 +66,14 @@ class _SettingPageState extends State { display = 'Current: v1.0.${BuildData.build}'; } return ListTile( - contentPadding: EdgeInsets.zero, - trailing: const Icon(Icons.keyboard_arrow_right), - title: Text(display, style: textStyle, - textAlign: TextAlign.start,), onTap: () => doUpdate(context, force: true)); + contentPadding: EdgeInsets.zero, + trailing: const Icon(Icons.keyboard_arrow_right), + title: Text( + display, + style: textStyle, + textAlign: TextAlign.start, + ), + onTap: () => doUpdate(context, force: true)); }); } diff --git a/lib/view/page/snippet/edit.dart b/lib/view/page/snippet/edit.dart index 6b09a206..0c18ac90 100644 --- a/lib/view/page/snippet/edit.dart +++ b/lib/view/page/snippet/edit.dart @@ -1,15 +1,91 @@ +import 'package:after_layout/after_layout.dart'; import 'package:flutter/material.dart'; +import 'package:toolbox/core/utils.dart'; +import 'package:toolbox/data/model/server/snippet.dart'; +import 'package:toolbox/data/provider/snippet.dart'; +import 'package:toolbox/locator.dart'; +import 'package:toolbox/view/widget/input_decoration.dart'; class SnippetEditPage extends StatefulWidget { - const SnippetEditPage({Key? key}) : super(key: key); + const SnippetEditPage({Key? key, this.snippet}) : super(key: key); + + final Snippet? snippet; @override _SnippetEditPageState createState() => _SnippetEditPageState(); } -class _SnippetEditPageState extends State { +class _SnippetEditPageState extends State + with AfterLayoutMixin { + final nameController = TextEditingController(); + final scriptController = TextEditingController(); + + late SnippetProvider _provider; + + @override + void initState() { + super.initState(); + _provider = locator(); + } + @override Widget build(BuildContext context) { - return Scaffold(appBar: AppBar(title: const Text('Snippet Edit'),), body: const Center(child: Text('Developing'),),); + return Scaffold( + appBar: AppBar(title: const Text('Edit'), actions: [ + widget.snippet != null + ? IconButton( + onPressed: () { + _provider.delInfo(widget.snippet!); + Navigator.of(context).pop(); + }, + icon: const Icon(Icons.delete)) + : const SizedBox() + ]), + body: ListView( + padding: const EdgeInsets.all(13), + children: [ + TextField( + controller: nameController, + keyboardType: TextInputType.text, + decoration: buildDecoration('Name', icon: Icons.info), + ), + TextField( + controller: scriptController, + autocorrect: false, + minLines: 3, + maxLines: 10, + keyboardType: TextInputType.text, + enableSuggestions: false, + decoration: buildDecoration('Snippet', icon: Icons.code), + ), + ], + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.send), + onPressed: () { + final name = nameController.text; + final script = scriptController.text; + if (name.isEmpty || script.isEmpty) { + showSnackBar(context, const Text('Two fields must not be empty.')); + return; + } + final snippet = Snippet(name, script); + if (widget.snippet != null) { + _provider.updateInfo(widget.snippet!, snippet); + } else { + _provider.addInfo(snippet); + } + Navigator.of(context).pop(); + }, + ), + ); + } + + @override + void afterFirstLayout(BuildContext context) { + if (widget.snippet != null) { + nameController.text = widget.snippet!.name; + scriptController.text = widget.snippet!.script; + } } } diff --git a/lib/view/page/snippet/list.dart b/lib/view/page/snippet/list.dart index 63ede276..082f60ca 100644 --- a/lib/view/page/snippet/list.dart +++ b/lib/view/page/snippet/list.dart @@ -1,4 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:toolbox/core/route.dart'; +import 'package:toolbox/core/utils.dart'; +import 'package:toolbox/data/model/server/snippet.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/locator.dart'; +import 'package:toolbox/view/page/snippet/edit.dart'; +import 'package:toolbox/view/widget/round_rect_card.dart'; class SnippetListPage extends StatefulWidget { const SnippetListPage({Key? key}) : super(key: key); @@ -8,8 +18,121 @@ class SnippetListPage extends StatefulWidget { } class _SnippetListPageState extends State { + int _selectedIndex = 0; + + final _textStyle = TextStyle(color: primaryColor); @override Widget build(BuildContext context) { - return Scaffold(appBar: AppBar(title: const Text('Snippet List'),), body: const Center(child: Text('Developing'),),); + return Scaffold( + appBar: AppBar( + title: const Text('Snippet List'), + ), + body: _buildBody(), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () => + AppRoute(const SnippetEditPage(), 'snippet edit page').go(context), + ), + ); + } + + Widget _buildBody() { + return Consumer( + builder: (_, key, __) { + return key.snippets.isNotEmpty + ? ListView.builder( + padding: const EdgeInsets.all(13), + itemCount: key.snippets.length, + itemExtent: 57, + itemBuilder: (context, idx) { + return RoundRectCard(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( + 'Edit', + style: _textStyle, + )), + TextButton( + onPressed: () => _showRunDialog(key.snippets[idx]), + child: Text( + 'Run', + style: _textStyle, + )) + ]) + ], + )); + }) + : const Center(child: Text('No saved snippets.')); + }, + ); + } + + void _showRunDialog(Snippet snippet) { + showRoundDialog(context, 'Choose destination', + Consumer(builder: (_, provider, __) { + if (provider.servers.isEmpty) { + return const Text('No server available'); + } + return SizedBox( + height: 111, + child: Stack(children: [ + Positioned( + child: Container( + height: 37, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(7)), + color: Colors.black12, + ), + ), + top: 36, + bottom: 36, + left: 0, + right: 0, + ), + ListWheelScrollView.useDelegate( + itemExtent: 37, + diameterRatio: 1.2, + controller: FixedExtentScrollController(initialItem: 0), + onSelectedItemChanged: (idx) => _selectedIndex = idx, + 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 { + final result = await locator() + .runSnippet(_selectedIndex, snippet); + if (result != null) { + showRoundDialog(context, 'Result', Text(result), [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Close')) + ]); + } + }, + child: const Text('Run')), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel')), + ]); } }