This commit is contained in:
lollipopkit
2023-05-08 16:25:31 +08:00
parent a2361da560
commit 7f16c27dcf
25 changed files with 634 additions and 572 deletions

View File

@@ -166,9 +166,9 @@ void showSnippetDialog(
showRoundDialog(
context: context,
title: Text(s.chooseDestination),
child: buildPicker(
provider.snippets.map((e) => Text(e.name)).toList(),
(idx) => snippet = provider.snippets[idx],
child: Picker(
items: provider.snippets.map((e) => Text(e.name)).toList(),
onSelected: (idx) => snippet = provider.snippets[idx],
),
actions: [
TextButton(

View File

@@ -5,28 +5,29 @@ import 'package:toolbox/locator.dart';
class PrivateKeyProvider extends BusyProvider {
List<PrivateKeyInfo> get infos => _infos;
final _store = locator<PrivateKeyStore>();
late List<PrivateKeyInfo> _infos;
void loadData() {
_infos = locator<PrivateKeyStore>().fetch();
_infos = _store.fetch();
}
void addInfo(PrivateKeyInfo info) {
_infos.add(info);
locator<PrivateKeyStore>().put(info);
_store.put(info);
notifyListeners();
}
void delInfo(PrivateKeyInfo info) {
_infos.removeWhere((e) => e.id == info.id);
locator<PrivateKeyStore>().delete(info);
_store.delete(info);
notifyListeners();
}
void updateInfo(PrivateKeyInfo old, PrivateKeyInfo newInfo) {
final idx = _infos.indexWhere((e) => e.id == old.id);
_infos[idx] = newInfo;
locator<PrivateKeyStore>().put(newInfo);
_store.put(newInfo);
notifyListeners();
}
}

View File

@@ -22,15 +22,18 @@ typedef ServersMap = Map<String, Server>;
class ServerProvider extends BusyProvider {
final ServersMap _servers = {};
ServersMap get servers => _servers;
final _limiter = TryLimiter();
Timer? _timer;
final _logger = Logger('SERVER');
final _store = locator<ServerStore>();
Future<void> loadLocalData() async {
setBusyState(true);
final infos = locator<ServerStore>().fetch();
final infos = _store.fetch();
for (final info in infos) {
_servers[info.id] = genServer(info);
}
@@ -103,20 +106,20 @@ class ServerProvider extends BusyProvider {
void addServer(ServerPrivateInfo spi) {
_servers[spi.id] = genServer(spi);
notifyListeners();
locator<ServerStore>().put(spi);
_store.put(spi);
refreshData(spi: spi);
}
void delServer(String id) {
_servers.remove(id);
notifyListeners();
locator<ServerStore>().delete(id);
_store.delete(id);
}
Future<void> updateServer(
ServerPrivateInfo old, ServerPrivateInfo newSpi) async {
_servers.remove(old.id);
locator<ServerStore>().update(old, newSpi);
_store.update(old, newSpi);
_servers[newSpi.id] = genServer(newSpi);
_servers[newSpi.id]?.client = await genClient(newSpi);
notifyListeners();

View File

@@ -7,23 +7,24 @@ import 'package:toolbox/locator.dart';
class SnippetProvider extends BusyProvider {
List<Snippet> get snippets => _snippets;
final _store = locator<SnippetStore>();
late List<Snippet> _snippets;
void loadData() {
_snippets = locator<SnippetStore>().fetch();
_snippets = _store.fetch();
}
void add(Snippet snippet) {
if (have(snippet)) return;
_snippets.add(snippet);
locator<SnippetStore>().put(snippet);
_store.put(snippet);
notifyListeners();
}
void del(Snippet snippet) {
if (!have(snippet)) return;
_snippets.removeAt(index(snippet));
locator<SnippetStore>().delete(snippet);
_store.delete(snippet);
notifyListeners();
}
@@ -38,7 +39,7 @@ class SnippetProvider extends BusyProvider {
void update(Snippet old, Snippet newOne) {
if (!have(old)) return;
_snippets[index(old)] = newOne;
locator<SnippetStore>().put(newOne);
_store.put(newOne);
notifyListeners();
}

View File

@@ -2,8 +2,8 @@
class BuildData {
static const String name = "ServerBox";
static const int build = 287;
static const int build = 288;
static const String engine = "3.7.11";
static const String buildAt = "2023-05-07 18:25:45.312302";
static const int modifications = 2;
static const String buildAt = "2023-05-07 20:47:03.124092";
static const int modifications = 1;
}

View File

@@ -33,3 +33,15 @@ const popMenuChild = Padding(
size: 21,
),
);
const centerLoading = Center(child: CircularProgressIndicator());
const centerSizedLoading = SizedBox(
width: 77,
height: 77,
child: Center(
child: CircularProgressIndicator(),
),
);
const loadingIcon = IconButton(onPressed: null, icon: centerLoading);

View File

@@ -6,6 +6,7 @@ const issueUrl = '$myGithub/flutter_server_box/issues';
// Thanks
const thanksMap = {
'its-tom': 'https://github.com/its-tom',
'RainSunMe': 'https://github.com/RainSunMe',
'fecture': 'https://github.com/fecture',
'Tao173': 'https://github.com/Tao173',

View File

@@ -32,74 +32,78 @@ class BackupPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final media = MediaQuery.of(context);
final s = S.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(s.backupAndRestore, style: textSize18),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(37),
child: Text(
s.backupTip,
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 107,
),
_buildCard(s.restore, Icons.download, media, () async {
final path = await pickOneFile();
if (path == null) {
showSnackBar(context, Text(s.notSelected));
return;
}
final file = File(path);
if (!file.existsSync()) {
showSnackBar(context, Text(s.fileNotExist(path)));
return;
}
final text = await file.readAsString();
_import(text, context, s);
}),
const SizedBox(height: 17),
const SizedBox(
width: 37,
child: Divider(),
),
const SizedBox(height: 17),
_buildCard(
s.backup,
Icons.file_upload,
media,
() async {
final result = _diyEncrtpt(
json.encode(
Backup(
backupFormatVersion,
DateTime.now().toString().split('.').first,
_server.fetch(),
_snippet.fetch(),
_privateKey.fetch(),
_dockerHosts.fetch(),
),
),
);
final path = '${(await docDir).path}/srvbox_bak.json';
await File(path).writeAsString(result);
await shareFiles(context, [path]);
},
)
],
)),
body: _buildBody(context, s),
);
}
Widget _buildBody(BuildContext context, S s) {
final media = MediaQuery.of(context);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(37),
child: Text(
s.backupTip,
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 107,
),
_buildCard(s.restore, Icons.download, media, () async {
final path = await pickOneFile();
if (path == null) {
showSnackBar(context, Text(s.notSelected));
return;
}
final file = File(path);
if (!file.existsSync()) {
showSnackBar(context, Text(s.fileNotExist(path)));
return;
}
final text = await file.readAsString();
_import(text, context, s);
}),
const SizedBox(height: 17),
const SizedBox(
width: 37,
child: Divider(),
),
const SizedBox(height: 17),
_buildCard(
s.backup,
Icons.file_upload,
media,
() async {
final result = _diyEncrtpt(
json.encode(
Backup(
backupFormatVersion,
DateTime.now().toString().split('.').first,
_server.fetch(),
_snippet.fetch(),
_privateKey.fetch(),
_dockerHosts.fetch(),
),
),
);
final path = '${(await docDir).path}/srvbox_bak.json';
await File(path).writeAsString(result);
await shareFiles(context, [path]);
},
)
],
));
}
Widget _buildCard(String text, IconData icon, MediaQueryData media,
FutureOr Function() onTap) {
final textColor = primaryColor.isBrightColor ? Colors.black : Colors.white;

View File

@@ -89,7 +89,7 @@ class _ConvertPageState extends State<ConvertPage>
Widget _buildInputTop() {
return SizedBox(
height: _media.size.height * 0.33,
child: buildInput(controller: _textEditingController),
child: Input(controller: _textEditingController),
);
}
@@ -163,7 +163,7 @@ class _ConvertPageState extends State<ConvertPage>
Widget _buildResult() {
return SizedBox(
height: _media.size.height * 0.33,
child: buildInput(controller: _textEditingControllerResult),
child: Input(controller: _textEditingControllerResult),
);
}

View File

@@ -15,7 +15,6 @@ import '../../data/res/ui.dart';
import '../../data/res/url.dart';
import '../../data/store/docker.dart';
import '../../locator.dart';
import '../widget/center_loading.dart';
import '../widget/dropdown_menu.dart';
import '../widget/round_rect_card.dart';
import '../widget/two_line_text.dart';
@@ -95,26 +94,23 @@ class _DockerManagePageState extends State<DockerManagePage> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildInput(
Input(
type: TextInputType.text,
label: _s.dockerImage,
hint: 'xxx:1.1',
controller: imageCtrl,
autoCorrect: false,
),
buildInput(
Input(
type: TextInputType.text,
controller: nameCtrl,
label: _s.dockerContainerName,
hint: 'xxx',
autoCorrect: false,
),
buildInput(
Input(
type: TextInputType.text,
controller: argsCtrl,
label: _s.extraArgs,
hint: '-p 2222:22 -v ~/.xxx/:/xxx',
autoCorrect: false,
),
],
),
@@ -206,7 +202,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
await showRoundDialog(
context: context,
title: Text(widget.spi.user),
child: buildInput(
child: Input(
controller: _textController,
type: TextInputType.visiblePassword,
obscureText: true,
@@ -379,9 +375,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
await showRoundDialog(
context: context,
title: Text(_s.dockerEditHost),
child: buildInput(
child: Input(
maxLines: 1,
autoCorrect: false,
controller:
TextEditingController(text: 'unix:///run/user/1000/docker.sock'),
onSubmitted: (value) {

View File

@@ -57,7 +57,7 @@ class _PingPageState extends State<PingPage>
child: Column(
children: [
const SizedBox(height: 13),
buildInput(
Input(
controller: _textEditingController,
hint: s.inputDomainHere,
maxLines: 1,

View File

@@ -12,7 +12,6 @@ import '../../data/provider/pkg.dart';
import '../../data/provider/server.dart';
import '../../data/res/ui.dart';
import '../../locator.dart';
import '../widget/center_loading.dart';
import '../widget/round_rect_card.dart';
import '../widget/two_line_text.dart';
@@ -31,7 +30,7 @@ class _PkgManagePageState extends State<PkgManagePage>
final _scrollController = ScrollController();
final _scrollControllerUpdate = ScrollController();
final _textController = TextEditingController();
final _aptProvider = locator<PkgProvider>();
final _pkgProvider = locator<PkgProvider>();
late S _s;
@override
@@ -44,7 +43,7 @@ class _PkgManagePageState extends State<PkgManagePage>
@override
void dispose() {
super.dispose();
locator<PkgProvider>().clear();
_pkgProvider.clear();
}
@override
@@ -57,7 +56,7 @@ class _PkgManagePageState extends State<PkgManagePage>
return;
}
_aptProvider.init(
_pkgProvider.init(
si.client!,
si.status.sysVer.dist,
() =>
@@ -67,7 +66,21 @@ class _PkgManagePageState extends State<PkgManagePage>
onPwdRequest,
widget.spi.user,
);
_aptProvider.refresh();
_pkgProvider.refresh();
}
@override
Widget build(BuildContext context) {
return Consumer<PkgProvider>(builder: (_, pkg, __) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: TwoLineText(up: _s.pkg, down: widget.spi.name),
),
body: _buildBody(pkg),
floatingActionButton: _buildFAB(pkg),
);
});
}
void onSubmitted() {
@@ -92,7 +105,7 @@ class _PkgManagePageState extends State<PkgManagePage>
await showRoundDialog(
context: context,
title: Text(widget.spi.user),
child: buildInput(
child: Input(
controller: _textController,
type: TextInputType.visiblePassword,
obscureText: true,
@@ -118,20 +131,6 @@ class _PkgManagePageState extends State<PkgManagePage>
return _textController.text.trim();
}
@override
Widget build(BuildContext context) {
return Consumer<PkgProvider>(builder: (_, pkg, __) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: TwoLineText(up: _s.pkg, down: widget.spi.name),
),
body: _buildBody(pkg),
floatingActionButton: _buildFAB(pkg),
);
});
}
Widget _buildFAB(PkgProvider pkg) {
if (pkg.isBusy || (pkg.upgradeable?.isEmpty ?? true)) {
return const SizedBox();

View File

@@ -61,128 +61,138 @@ 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!);
context.pop();
},
icon: const Icon(Icons.delete))
: const SizedBox()
],
),
body: ListView(
padding: const EdgeInsets.all(13),
children: [
buildInput(
controller: _nameController,
type: TextInputType.text,
node: _nameNode,
onSubmitted: (_) => _focusScope.requestFocus(_keyNode),
label: _s.name,
icon: Icons.info,
),
buildInput(
controller: _keyController,
autoCorrect: false,
minLines: 3,
maxLines: 10,
type: TextInputType.text,
node: _keyNode,
onSubmitted: (_) => _focusScope.requestFocus(_pwdNode),
label: _s.privateKey,
icon: Icons.vpn_key,
),
TextButton(
onPressed: () async {
final path = await pickOneFile();
if (path == null) {
showSnackBar(context, const Text('path is null'));
return;
}
appBar: _buildAppBar(),
body: _buildBody(),
floatingActionButton: _buildFAB(),
);
}
final file = File(path);
if (!file.existsSync()) {
showSnackBar(context, Text(_s.fileNotExist(path)));
return;
}
final size = (await file.stat()).size;
if (size > privateKeyMaxSize) {
showSnackBar(
context,
Text(
_s.fileTooLarge(
path,
size.convertBytes,
privateKeyMaxSize.convertBytes,
),
),
);
return;
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
title: Text(_s.edit, style: textSize18),
actions: [
widget.info != null
? IconButton(
tooltip: _s.delete,
onPressed: () {
_provider.delInfo(widget.info!);
context.pop();
},
icon: const Icon(Icons.delete))
: const SizedBox()
],
);
}
_keyController.text = await file.readAsString();
},
child: Text(_s.pickFile),
),
buildInput(
controller: _pwdController,
autoCorrect: false,
type: TextInputType.text,
node: _pwdNode,
obscureText: true,
label: _s.pwd,
icon: Icons.password,
),
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
_loading
],
),
floatingActionButton: FloatingActionButton(
tooltip: _s.save,
onPressed: () async {
final name = _nameController.text;
final key = _keyController.text.trim();
final pwd = _pwdController.text;
if (name.isEmpty || key.isEmpty) {
showSnackBar(context, Text(_s.fieldMustNotEmpty));
return;
}
FocusScope.of(context).unfocus();
Widget _buildFAB() {
return FloatingActionButton(
tooltip: _s.save,
onPressed: () async {
final name = _nameController.text;
final key = _keyController.text.trim();
final pwd = _pwdController.text;
if (name.isEmpty || key.isEmpty) {
showSnackBar(context, Text(_s.fieldMustNotEmpty));
return;
}
FocusScope.of(context).unfocus();
setState(() {
_loading = const SizedBox(
height: 50,
child: Center(
child: CircularProgressIndicator(),
),
);
});
final info = PrivateKeyInfo(name, key, '');
bool haveErr = false;
try {
info.privateKey = await compute(decyptPem, [key, pwd]);
} catch (e) {
showSnackBar(context, Text(e.toString()));
haveErr = true;
} finally {
setState(() {
_loading = const SizedBox(
height: 50,
child: Center(
child: CircularProgressIndicator(),
),
);
_loading = const SizedBox();
});
final info = PrivateKeyInfo(name, key, '');
bool haveErr = false;
try {
info.privateKey = await compute(decyptPem, [key, pwd]);
} catch (e) {
showSnackBar(context, Text(e.toString()));
haveErr = true;
} finally {
setState(() {
_loading = const SizedBox();
});
}
if (haveErr) return;
if (widget.info != null) {
_provider.updateInfo(widget.info!, info);
} else {
_provider.addInfo(info);
}
context.pop();
},
child: const Icon(Icons.save),
),
}
if (haveErr) return;
if (widget.info != null) {
_provider.updateInfo(widget.info!, info);
} else {
_provider.addInfo(info);
}
context.pop();
},
child: const Icon(Icons.save),
);
}
Widget _buildBody() {
return ListView(
padding: const EdgeInsets.all(13),
children: [
Input(
controller: _nameController,
type: TextInputType.text,
node: _nameNode,
onSubmitted: (_) => _focusScope.requestFocus(_keyNode),
label: _s.name,
icon: Icons.info,
),
Input(
controller: _keyController,
minLines: 3,
maxLines: 10,
type: TextInputType.text,
node: _keyNode,
onSubmitted: (_) => _focusScope.requestFocus(_pwdNode),
label: _s.privateKey,
icon: Icons.vpn_key,
),
TextButton(
onPressed: () async {
final path = await pickOneFile();
if (path == null) {
showSnackBar(context, const Text('path is null'));
return;
}
final file = File(path);
if (!file.existsSync()) {
showSnackBar(context, Text(_s.fileNotExist(path)));
return;
}
final size = (await file.stat()).size;
if (size > privateKeyMaxSize) {
showSnackBar(
context,
Text(
_s.fileTooLarge(
path,
size.convertBytes,
privateKeyMaxSize.convertBytes,
),
),
);
return;
}
_keyController.text = await file.readAsString();
},
child: Text(_s.pickFile),
),
Input(
controller: _pwdController,
type: TextInputType.text,
node: _pwdNode,
obscureText: true,
label: _s.pwd,
icon: Icons.password,
),
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
_loading
],
);
}

View File

@@ -30,41 +30,44 @@ class _PrivateKeyListState extends State<PrivateKeysListPage> {
appBar: AppBar(
title: Text(_s.privateKey, style: textSize18),
),
body: Consumer<PrivateKeyProvider>(
builder: (_, key, __) {
if (key.infos.isEmpty) {
return Center(
child: Text(_s.noSavedPrivateKey),
);
}
return ListView.builder(
padding: const EdgeInsets.all(13),
itemCount: key.infos.length,
itemBuilder: (context, idx) {
return RoundRectCard(
ListTile(
title: Text(
key.infos[idx].id,
),
trailing: TextButton(
onPressed: () => AppRoute(
PrivateKeyEditPage(info: key.infos[idx]),
'private key edit page',
).go(context),
child: Text(_s.edit),
),
),
);
},
);
},
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () =>
AppRoute(const PrivateKeyEditPage(), 'private key edit page')
.go(context),
onPressed: () => AppRoute(
const PrivateKeyEditPage(),
'private key edit page',
).go(context),
),
);
}
Widget _buildBody() {
return Consumer<PrivateKeyProvider>(
builder: (_, key, __) {
if (key.infos.isEmpty) {
return Center(
child: Text(_s.noSavedPrivateKey),
);
}
return ListView.builder(
padding: const EdgeInsets.all(13),
itemCount: key.infos.length,
itemBuilder: (context, idx) {
return RoundRectCard(
ListTile(
title: Text(key.infos[idx].id),
trailing: TextButton(
onPressed: () => AppRoute(
PrivateKeyEditPage(info: key.infos[idx]),
'private key edit page',
).go(context),
child: Text(_s.edit),
),
),
);
},
);
},
);
}
}

View File

@@ -108,7 +108,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildInput(
Input(
controller: _nameController,
type: TextInputType.text,
node: _nameFocus,
@@ -117,17 +117,16 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
label: _s.name,
icon: Icons.info,
),
buildInput(
Input(
controller: _ipController,
type: TextInputType.text,
onSubmitted: (_) => _focusScope.requestFocus(_portFocus),
node: _ipFocus,
autoCorrect: false,
label: _s.host,
icon: Icons.storage,
hint: 'example.com',
),
buildInput(
Input(
controller: _portController,
type: TextInputType.number,
node: _portFocus,
@@ -136,11 +135,10 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
icon: Icons.format_list_numbered,
hint: '22',
),
buildInput(
Input(
controller: _usernameController,
type: TextInputType.text,
node: _usernameFocus,
autoCorrect: false,
label: _s.user,
icon: Icons.account_box,
hint: 'root',
@@ -158,7 +156,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
],
),
!usePublicKey
? buildInput(
? Input(
controller: _passwordController,
obscureText: true,
type: TextInputType.text,

View File

@@ -62,38 +62,7 @@ class _ServerPageState extends State<ServerPage>
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: RefreshIndicator(
onRefresh: () async =>
await _serverProvider.refreshData(onlyFailed: true),
child: Consumer<ServerProvider>(
builder: (_, pro, __) {
if (pro.servers.isEmpty) {
return Center(
child: Text(
_s.serverTabEmpty,
textAlign: TextAlign.center,
),
);
}
final keys = pro.servers.keys.toList();
return ListView.separated(
padding: const EdgeInsets.fromLTRB(7, 10, 7, 7),
controller: ScrollController(),
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (ctx, idx) {
if (idx == pro.servers.length) {
return SizedBox(height: _media.padding.bottom);
}
return _buildEachServerCard(pro.servers[keys[idx]]);
},
itemCount: pro.servers.length,
separatorBuilder: (_, __) => const SizedBox(
height: 3,
),
);
},
),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: () => AppRoute(
const ServerEditPage(),
@@ -106,6 +75,41 @@ class _ServerPageState extends State<ServerPage>
);
}
Widget _buildBody() {
return RefreshIndicator(
onRefresh: () async =>
await _serverProvider.refreshData(onlyFailed: true),
child: Consumer<ServerProvider>(
builder: (_, pro, __) {
if (pro.servers.isEmpty) {
return Center(
child: Text(
_s.serverTabEmpty,
textAlign: TextAlign.center,
),
);
}
final keys = pro.servers.keys.toList();
return ListView.separated(
padding: const EdgeInsets.fromLTRB(7, 10, 7, 7),
controller: ScrollController(),
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (ctx, idx) {
if (idx == pro.servers.length) {
return SizedBox(height: _media.padding.bottom);
}
return _buildEachServerCard(pro.servers[keys[idx]]);
},
itemCount: pro.servers.length,
separatorBuilder: (_, __) => const SizedBox(
height: 3,
),
);
},
),
);
}
Widget _buildEachServerCard(Server? si) {
if (si == null) {
return const SizedBox();

View File

@@ -8,7 +8,6 @@ import '../../../core/utils/ui.dart';
import '../../../data/model/sftp/download_status.dart';
import '../../../data/provider/sftp_download.dart';
import '../../../data/res/ui.dart';
import '../../widget/center_loading.dart';
import '../../widget/round_rect_card.dart';
class SFTPDownloadingPage extends StatefulWidget {

View File

@@ -18,9 +18,9 @@ import '../../../data/model/sftp/download_item.dart';
import '../../../data/provider/server.dart';
import '../../../data/provider/sftp_download.dart';
import '../../../data/res/path.dart';
import '../../../data/res/ui.dart';
import '../../../data/store/private_key.dart';
import '../../../locator.dart';
import '../../widget/center_loading.dart';
import '../../widget/fade_in.dart';
import '../../widget/two_line_text.dart';
import 'downloading.dart';
@@ -144,7 +144,7 @@ class _SFTPPageState extends State<SFTPPage> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildInput(
Input(
label: _s.path,
hint: '/',
onSubmitted: (value) => context.pop(value),
@@ -378,7 +378,7 @@ class _SFTPPageState extends State<SFTPPage> {
showRoundDialog(
context: context,
title: Text(_s.createFolder),
child: buildInput(
child: Input(
controller: textController,
label: _s.name,
),
@@ -422,7 +422,7 @@ class _SFTPPageState extends State<SFTPPage> {
showRoundDialog(
context: context,
title: Text(_s.createFile),
child: buildInput(
child: Input(
controller: textController,
label: _s.name,
),
@@ -467,7 +467,7 @@ class _SFTPPageState extends State<SFTPPage> {
showRoundDialog(
context: context,
title: Text(_s.rename),
child: buildInput(
child: Input(
controller: textController,
label: _s.name,
),

View File

@@ -57,47 +57,53 @@ class _SnippetEditPageState extends State<SnippetEditPage>
: const SizedBox()
],
),
body: ListView(
padding: const EdgeInsets.all(13),
children: [
buildInput(
controller: _nameController,
type: TextInputType.text,
onSubmitted: (_) =>
FocusScope.of(context).requestFocus(_scriptNode),
label: _s.name,
icon: Icons.info,
),
buildInput(
controller: _scriptController,
autoCorrect: false,
node: _scriptNode,
minLines: 3,
maxLines: 10,
type: TextInputType.text,
label: _s.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, Text(_s.fieldMustNotEmpty));
return;
}
final snippet = Snippet(name, script);
if (widget.snippet != null) {
_provider.update(widget.snippet!, snippet);
} else {
_provider.add(snippet);
}
context.pop();
},
),
body: _buildBody(),
floatingActionButton: _buildFAB(),
);
}
Widget _buildFAB() {
return FloatingActionButton(
child: const Icon(Icons.send),
onPressed: () {
final name = _nameController.text;
final script = _scriptController.text;
if (name.isEmpty || script.isEmpty) {
showSnackBar(context, Text(_s.fieldMustNotEmpty));
return;
}
final snippet = Snippet(name, script);
if (widget.snippet != null) {
_provider.update(widget.snippet!, snippet);
} else {
_provider.add(snippet);
}
context.pop();
},
);
}
Widget _buildBody() {
return ListView(
padding: const EdgeInsets.all(13),
children: [
Input(
controller: _nameController,
type: TextInputType.text,
onSubmitted: (_) => FocusScope.of(context).requestFocus(_scriptNode),
label: _s.name,
icon: Icons.info,
),
Input(
controller: _scriptController,
node: _scriptNode,
minLines: 3,
maxLines: 10,
type: TextInputType.text,
label: _s.snippet,
icon: Icons.code,
),
],
);
}

View File

@@ -76,61 +76,6 @@ class _SSHPageState extends State<SSHPage> {
super.dispose();
}
void _write(String p0) {
_terminal.write('$p0\r\n');
}
Future<void> initTerminal() async {
_write('Connecting...\r\n');
_client = await genClient(
widget.spi,
onStatus: (p0) {
switch (p0) {
case GenSSHClientStatus.socket:
_write('Destination: ${widget.spi.id}');
return _write('Establishing socket...');
case GenSSHClientStatus.key:
return _write('Using private key to connect...');
case GenSSHClientStatus.pwd:
return _write('Sending password to auth...');
}
},
);
_write('Connected\r\n');
_write('Terminal size: ${_terminal.viewWidth}x${_terminal.viewHeight}\r\n');
_write('Starting shell...\r\n');
final session = await _client!.shell(
pty: SSHPtyConfig(
width: _terminal.viewWidth,
height: _terminal.viewHeight,
),
);
_terminal.buffer.clear();
_terminal.buffer.setCursor(0, 0);
_terminal.onOutput = (data) {
session.write(utf8.encode(data) as Uint8List);
};
_listen(session.stdout);
_listen(session.stderr);
await session.done;
if (mounted) {
context.pop();
}
}
void _listen(Stream<Uint8List> stream) {
stream
.cast<List<int>>()
.transform(const Utf8Decoder())
.listen(_terminal.write);
}
@override
Widget build(BuildContext context) {
Widget child = Scaffold(
@@ -357,4 +302,59 @@ class _SSHPageState extends State<SSHPage> {
},
);
}
void _write(String p0) {
_terminal.write('$p0\r\n');
}
Future<void> initTerminal() async {
_write('Connecting...\r\n');
_client = await genClient(
widget.spi,
onStatus: (p0) {
switch (p0) {
case GenSSHClientStatus.socket:
_write('Destination: ${widget.spi.id}');
return _write('Establishing socket...');
case GenSSHClientStatus.key:
return _write('Using private key to connect...');
case GenSSHClientStatus.pwd:
return _write('Sending password to auth...');
}
},
);
_write('Connected\r\n');
_write('Terminal size: ${_terminal.viewWidth}x${_terminal.viewHeight}\r\n');
_write('Starting shell...\r\n');
final session = await _client!.shell(
pty: SSHPtyConfig(
width: _terminal.viewWidth,
height: _terminal.viewHeight,
),
);
_terminal.buffer.clear();
_terminal.buffer.setCursor(0, 0);
_terminal.onOutput = (data) {
session.write(utf8.encode(data) as Uint8List);
};
_listen(session.stdout);
_listen(session.stderr);
await session.done;
if (mounted) {
context.pop();
}
}
void _listen(Stream<Uint8List> stream) {
stream
.cast<List<int>>()
.transform(const Utf8Decoder())
.listen(_terminal.write);
}
}

View File

@@ -1,13 +0,0 @@
import 'package:flutter/material.dart';
const centerLoading = Center(child: CircularProgressIndicator());
const centerSizedLoading = SizedBox(
width: 77,
height: 77,
child: Center(
child: CircularProgressIndicator(),
),
);
final loadingIcon = IconButton(onPressed: () {}, icon: centerLoading);

View File

@@ -1,38 +1,59 @@
import 'package:flutter/material.dart';
import 'package:toolbox/view/widget/round_rect_card.dart';
Widget buildInput({
TextEditingController? controller,
int maxLines = 1,
int? minLines,
String? hint,
String? label,
Function(String)? onSubmitted,
bool obscureText = false,
IconData? icon,
TextInputType? type,
FocusNode? node,
bool autoCorrect = true,
}) {
return RoundRectCard(
Padding(
padding: const EdgeInsets.symmetric(horizontal: 17),
child: TextField(
maxLines: maxLines,
minLines: minLines,
onSubmitted: onSubmitted,
keyboardType: type,
focusNode: node,
autocorrect: autoCorrect,
decoration: InputDecoration(
label: label != null ? Text(label) : null,
hintText: hint,
icon: icon != null ? Icon(icon) : null,
border: InputBorder.none,
import 'round_rect_card.dart';
class Input extends StatelessWidget {
final TextEditingController? controller;
final int maxLines;
final int? minLines;
final String? hint;
final String? label;
final Function(String)? onSubmitted;
final bool obscureText;
final IconData? icon;
final TextInputType? type;
final FocusNode? node;
final bool autoCorrect;
final bool suggestiion;
const Input({
super.key,
this.controller,
this.maxLines = 1,
this.minLines,
this.hint,
this.label,
this.onSubmitted,
this.obscureText = false,
this.icon,
this.type,
this.node,
this.autoCorrect = false,
this.suggestiion = false,
});
@override
Widget build(BuildContext context) {
return RoundRectCard(
Padding(
padding: const EdgeInsets.symmetric(horizontal: 17),
child: TextField(
maxLines: maxLines,
minLines: minLines,
onSubmitted: onSubmitted,
keyboardType: type,
focusNode: node,
autocorrect: autoCorrect,
enableSuggestions: suggestiion,
decoration: InputDecoration(
label: label != null ? Text(label!) : null,
hintText: hint,
icon: icon != null ? Icon(icon) : null,
border: InputBorder.none,
),
controller: controller,
obscureText: obscureText,
),
controller: controller,
obscureText: obscureText,
),
),
);
);
}
}

View File

@@ -1,42 +1,52 @@
import 'package:flutter/material.dart';
Widget buildPicker(
List<Widget> items,
Function(int idx) onSelected, {
double height = 157,
}) {
final pad = (height - 37) / 2;
return SizedBox(
height: height,
child: Stack(
children: [
Positioned(
top: pad,
bottom: pad,
left: 0,
right: 0,
child: Container(
height: 37,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(7)),
color: Colors.black12,
class Picker extends StatelessWidget {
final List<Widget> items;
final Function(int idx) onSelected;
final double height;
const Picker({
super.key,
required this.items,
required this.onSelected,
this.height = 157,
});
@override
Widget build(BuildContext context) {
final pad = (height - 37) / 2;
return SizedBox(
height: height,
child: Stack(
children: [
Positioned(
top: pad,
bottom: pad,
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: 2.7,
controller: FixedExtentScrollController(initialItem: 0),
onSelectedItemChanged: (idx) => onSelected(idx),
physics: const FixedExtentScrollPhysics(),
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => Center(
child: items[index],
ListWheelScrollView.useDelegate(
itemExtent: 37,
diameterRatio: 2.7,
controller: FixedExtentScrollController(initialItem: 0),
onSelectedItemChanged: (idx) => onSelected(idx),
physics: const FixedExtentScrollPhysics(),
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => Center(
child: items[index],
),
childCount: items.length,
),
childCount: items.length,
),
)
],
),
);
)
],
),
);
}
}