From 4274e8bed192a07ffbb467bbaf049f563c7c4327 Mon Sep 17 00:00:00 2001 From: Junyuan Feng Date: Thu, 7 Apr 2022 20:00:06 +0800 Subject: [PATCH] Snippet support import/export. --- lib/data/model/app/menu_item.dart | 3 +- lib/data/provider/snippet.dart | 26 ++++++-- lib/data/res/build_data.dart | 6 +- lib/view/page/snippet/edit.dart | 6 +- lib/view/page/snippet/list.dart | 100 ++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 13 deletions(-) diff --git a/lib/data/model/app/menu_item.dart b/lib/data/model/app/menu_item.dart index a9e3d615..5820bef0 100644 --- a/lib/data/model/app/menu_item.dart +++ b/lib/data/model/app/menu_item.dart @@ -29,7 +29,8 @@ class ServerTabMenuItems { static const sftp = MenuItem(text: 'SFTP', icon: Icons.insert_drive_file); static const snippet = MenuItem(text: 'Snippet', icon: Icons.label); - static const apt = MenuItem(text: 'Apt/Yum', icon: Icons.system_security_update); + static const apt = + MenuItem(text: 'Apt/Yum', icon: Icons.system_security_update); static const docker = MenuItem(text: 'Docker', icon: Icons.view_agenda); static const edit = MenuItem(text: 'Edit', icon: Icons.edit); } diff --git a/lib/data/provider/snippet.dart b/lib/data/provider/snippet.dart index 580a0d4a..7538bce2 100644 --- a/lib/data/provider/snippet.dart +++ b/lib/data/provider/snippet.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:toolbox/core/provider_base.dart'; import 'package:toolbox/data/model/server/snippet.dart'; import 'package:toolbox/data/store/snippet.dart'; @@ -11,22 +13,34 @@ class SnippetProvider extends BusyProvider { _snippets = locator().fetch(); } - void addInfo(Snippet snippet) { + void add(Snippet snippet) { + if (have(snippet)) return; _snippets.add(snippet); locator().put(snippet); notifyListeners(); } - void delInfo(Snippet snippet) { - _snippets.removeWhere((e) => e.name == snippet.name); + void del(Snippet snippet) { + if (!have(snippet)) return; + _snippets.removeAt(index(snippet)); locator().delete(snippet); notifyListeners(); } - void updateInfo(Snippet old, Snippet newOne) { - final idx = _snippets.indexWhere((e) => e.name == old.name); - _snippets[idx] = newOne; + int index(Snippet snippet) { + return _snippets.indexWhere((e) => e.name == snippet.name); + } + + bool have(Snippet snippet) { + return index(snippet) != -1; + } + + void update(Snippet old, Snippet newOne) { + if (!have(old)) return; + _snippets[index(old)] = newOne; locator().update(old, newOne); notifyListeners(); } + + String get export => json.encode(snippets); } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 6cc79da4..0ecdb6ba 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 110; + static const int build = 111; static const String engine = - "Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision c860cba910 (12 days ago) • 2022-03-25 00:23:12 -0500\nEngine • revision 57d3bac3dd\nTools • Dart 2.16.2 • DevTools 2.9.2\n"; - static const String buildAt = "2022-04-06 13:48:27.717376"; + "Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision c860cba910 (13 days ago) • 2022-03-25 00:23:12 -0500\nEngine • revision 57d3bac3dd\nTools • Dart 2.16.2 • DevTools 2.9.2\n"; + static const String buildAt = "2022-04-07 19:53:12.283804"; static const int modifications = 0; } diff --git a/lib/view/page/snippet/edit.dart b/lib/view/page/snippet/edit.dart index 0c18ac90..5270214a 100644 --- a/lib/view/page/snippet/edit.dart +++ b/lib/view/page/snippet/edit.dart @@ -35,7 +35,7 @@ class _SnippetEditPageState extends State widget.snippet != null ? IconButton( onPressed: () { - _provider.delInfo(widget.snippet!); + _provider.del(widget.snippet!); Navigator.of(context).pop(); }, icon: const Icon(Icons.delete)) @@ -71,9 +71,9 @@ class _SnippetEditPageState extends State } final snippet = Snippet(name, script); if (widget.snippet != null) { - _provider.updateInfo(widget.snippet!, snippet); + _provider.update(widget.snippet!, snippet); } else { - _provider.addInfo(snippet); + _provider.add(snippet); } Navigator.of(context).pop(); }, diff --git a/lib/view/page/snippet/list.dart b/lib/view/page/snippet/list.dart index 8f72d6f5..784668db 100644 --- a/lib/view/page/snippet/list.dart +++ b/lib/view/page/snippet/list.dart @@ -1,3 +1,4 @@ +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:toolbox/core/route.dart'; @@ -23,12 +24,20 @@ class SnippetListPage extends StatefulWidget { class _SnippetListPageState extends State { late ServerPrivateInfo _selectedIndex; + final _importFieldController = TextEditingController(); + final _exportFieldController = TextEditingController(); + final _textStyle = TextStyle(color: primaryColor); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Snippet List'), + actions: [ + IconButton( + onPressed: () => _showImportExport(), + icon: const Icon(Icons.import_export)), + ], ), body: _buildBody(), floatingActionButton: FloatingActionButton( @@ -39,6 +48,97 @@ class _SnippetListPageState extends State { ); } + Future _showImportExport() async { + await showRoundDialog( + context, + 'Choose', + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text('Import'), + leading: const Icon(Icons.download), + onTap: () => _showImportDialog(), + ), + ListTile( + title: const Text('Export'), + leading: const Icon(Icons.file_upload), + onTap: () => _showExportDialog(), + ), + ], + ), + []); + } + + Future _showExportDialog() async { + Navigator.of(context).pop(); + _exportFieldController.text = locator().export; + await showRoundDialog( + context, + 'Export', + TextField( + decoration: const InputDecoration( + labelText: 'JSON', + ), + maxLines: 3, + controller: _exportFieldController, + ), + [ + TextButton( + child: const Text('OK'), + onPressed: () => Navigator.pop(context), + ), + ]); + } + + Future _showImportDialog() async { + Navigator.of(context).pop(); + await showRoundDialog( + context, + 'Import', + TextField( + decoration: const InputDecoration( + labelText: 'Url or JSON', + ), + maxLines: 2, + controller: _importFieldController, + ), + [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel')), + TextButton( + onPressed: () async => + await _import(_importFieldController.text.trim()), + child: const Text('GO'), + ) + ]); + } + + Future _import(String text) async { + if (text.isEmpty) { + showSnackBar(context, const Text('field can not be empty')); + return; + } + final snippetProvider = locator(); + if (text.startsWith('http')) { + final resp = await Dio().get(text); + if (resp.statusCode != 200) { + showSnackBar( + context, Text('request failed, status code: ${resp.statusCode}')); + return; + } + for (final snippet in getSnippetList(resp.data)) { + snippetProvider.add(snippet); + } + } else { + for (final snippet in getSnippetList(text)) { + snippetProvider.add(snippet); + } + } + Navigator.of(context).pop(); + } + Widget _buildBody() { return Consumer( builder: (_, key, __) {