mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 15:24:35 +01:00
opt.: TagsEditor & Btn
This commit is contained in:
@@ -31,7 +31,7 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
|||||||
## 🔖 Feature
|
## 🔖 Feature
|
||||||
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process`...
|
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process`...
|
||||||
- Platform specific: `Bio auth`、`Msg push`、`Home widget`、`watchOS App`...
|
- Platform specific: `Bio auth`、`Msg push`、`Home widget`、`watchOS App`...
|
||||||
- English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic); Español, Русский язык, Português, 日本語 (Generated by GPT)
|
- English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic), Türkçe [@mikropsoft](https://github.com/mikropsoft); Español, Русский язык, Português, 日本語 (Generated by GPT)
|
||||||
|
|
||||||
|
|
||||||
## 🏙️ ScreenShots
|
## 🏙️ ScreenShots
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
|||||||
- 本地化
|
- 本地化
|
||||||
- English, 简体中文
|
- English, 简体中文
|
||||||
- Español, Русский язык, Português, 日本語 (Generated by GPT)
|
- Español, Русский язык, Português, 日本語 (Generated by GPT)
|
||||||
- Deutsch (@its-tom) / 繁體中文 (@kalashnikov) / Indonesian (@azkadev) / Français (@FrancXPT) / Dutch (@QazCetelic)
|
- Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic), Türkçe [@mikropsoft](https://github.com/mikropsoft);
|
||||||
|
|
||||||
|
|
||||||
## 🏙️ 截屏
|
## 🏙️ 截屏
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ class ServerProvider extends ChangeNotifier {
|
|||||||
Iterable<Server> get servers => _servers.values;
|
Iterable<Server> get servers => _servers.values;
|
||||||
final List<String> _serverOrder = [];
|
final List<String> _serverOrder = [];
|
||||||
List<String> get serverOrder => _serverOrder;
|
List<String> get serverOrder => _serverOrder;
|
||||||
final _tags = ValueNotifier(<String>[]);
|
final _tags = ValueNotifier(<String>{});
|
||||||
ValueNotifier<List<String>> get tags => _tags;
|
ValueNotifier<Set<String>> get tags => _tags;
|
||||||
|
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
|
|
||||||
@@ -85,7 +85,6 @@ class ServerProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateTags() {
|
void _updateTags() {
|
||||||
_tags.value.clear();
|
|
||||||
for (final s in _servers.values) {
|
for (final s in _servers.values) {
|
||||||
if (s.spi.tags == null) continue;
|
if (s.spi.tags == null) continue;
|
||||||
for (final t in s.spi.tags!) {
|
for (final t in s.spi.tags!) {
|
||||||
@@ -94,21 +93,7 @@ class ServerProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_tags.value.sort();
|
_tags.value = (_tags.value.toList()..sort()).toSet();
|
||||||
_tags.notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void renameTag(String old, String new_) {
|
|
||||||
for (final s in _servers.values) {
|
|
||||||
if (s.spi.tags == null) continue;
|
|
||||||
for (var i = 0; i < s.spi.tags!.length; i++) {
|
|
||||||
if (s.spi.tags![i] == old) {
|
|
||||||
s.spi.tags![i] = new_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Stores.server.update(s.spi, s.spi);
|
|
||||||
}
|
|
||||||
_updateTags();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Server genServer(ServerPrivateInfo spi) {
|
Server genServer(ServerPrivateInfo spi) {
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ class SnippetProvider extends ChangeNotifier {
|
|||||||
late List<Snippet> _snippets;
|
late List<Snippet> _snippets;
|
||||||
List<Snippet> get snippets => _snippets;
|
List<Snippet> get snippets => _snippets;
|
||||||
|
|
||||||
final _tags = ValueNotifier(<String>[]);
|
final tags = ValueNotifier(<String>{});
|
||||||
ValueNotifier<List<String>> get tags => _tags;
|
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
_snippets = Stores.snippet.fetch();
|
_snippets = Stores.snippet.fetch();
|
||||||
@@ -29,16 +28,14 @@ class SnippetProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateTags() {
|
void _updateTags() {
|
||||||
_tags.value.clear();
|
final tags_ = <String>{};
|
||||||
final tags = <String>{};
|
|
||||||
for (final s in _snippets) {
|
for (final s in _snippets) {
|
||||||
if (s.tags?.isEmpty ?? true) {
|
final t = s.tags;
|
||||||
continue;
|
if (t != null) {
|
||||||
|
tags_.addAll(t);
|
||||||
}
|
}
|
||||||
tags.addAll(s.tags!);
|
|
||||||
}
|
}
|
||||||
_tags.value.addAll(tags);
|
tags.value = tags_;
|
||||||
_tags.notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(Snippet snippet) {
|
void add(Snippet snippet) {
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
final _pveIgnoreCert = ValueNotifier(false);
|
final _pveIgnoreCert = ValueNotifier(false);
|
||||||
final _env = <String, String>{}.vn;
|
final _env = <String, String>{}.vn;
|
||||||
final _customCmds = <String, String>{}.vn;
|
final _customCmds = <String, String>{}.vn;
|
||||||
|
final _tags = <String>{}.vn;
|
||||||
var _tags = <String>[];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -170,12 +169,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
hint: 'root',
|
hint: 'root',
|
||||||
suggestion: false,
|
suggestion: false,
|
||||||
),
|
),
|
||||||
TagEditor(
|
TagTile(tags: _tags, allTags: Pros.server.tags.value).cardx,
|
||||||
tags: _tags,
|
|
||||||
onChanged: (p0) => _tags = p0,
|
|
||||||
allTags: [...Pros.server.tags.value],
|
|
||||||
onRenameTag: Pros.server.renameTag,
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(l10n.autoConnect),
|
title: Text(l10n.autoConnect),
|
||||||
trailing: ListenableBuilder(
|
trailing: ListenableBuilder(
|
||||||
@@ -436,7 +430,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
|
|
||||||
List<Widget> _buildWOLs() {
|
List<Widget> _buildWOLs() {
|
||||||
return [
|
return [
|
||||||
const Text('Wake On LAN', style: UIs.text13Grey),
|
const Text('Wake On LAN (beta)', style: UIs.text13Grey),
|
||||||
UIs.height7,
|
UIs.height7,
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(BoxIcons.bxs_help_circle),
|
leading: const Icon(BoxIcons.bxs_help_circle),
|
||||||
@@ -455,7 +449,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
controller: _wolIpCtrl,
|
controller: _wolIpCtrl,
|
||||||
type: TextInputType.text,
|
type: TextInputType.text,
|
||||||
label: 'IP ${l10n.addr}',
|
label: 'IP ${l10n.addr}',
|
||||||
icon: Icons.network_cell,
|
icon: ZondIcons.network,
|
||||||
hint: '192.168.1.x',
|
hint: '192.168.1.x',
|
||||||
suggestion: false,
|
suggestion: false,
|
||||||
),
|
),
|
||||||
@@ -590,7 +584,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
keyId: _keyIdx.value != null
|
keyId: _keyIdx.value != null
|
||||||
? Pros.key.pkis.elementAt(_keyIdx.value!).id
|
? Pros.key.pkis.elementAt(_keyIdx.value!).id
|
||||||
: null,
|
: null,
|
||||||
tags: _tags,
|
tags: _tags.value.isEmpty ? null : _tags.value.toList(),
|
||||||
alterUrl: _altUrlController.text.selfIfNotNullEmpty,
|
alterUrl: _altUrlController.text.selfIfNotNullEmpty,
|
||||||
autoConnect: _autoConnect.value,
|
autoConnect: _autoConnect.value,
|
||||||
jumpId: _jumpServer.value,
|
jumpId: _jumpServer.value,
|
||||||
@@ -616,7 +610,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
context.showRoundDialog(
|
context.showRoundDialog(
|
||||||
title: l10n.attention,
|
title: l10n.attention,
|
||||||
child: SimpleMarkdown(data: l10n.writeScriptTip),
|
child: SimpleMarkdown(data: l10n.writeScriptTip),
|
||||||
actions: Btns.oks(onTap: () => context.pop(true)),
|
actions: [Btn.ok()],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -648,7 +642,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// List in dart is passed by pointer, so you need to copy it here
|
/// List in dart is passed by pointer, so you need to copy it here
|
||||||
_tags.addAll(spi.tags ?? []);
|
_tags.value = spi.tags?.toSet() ?? {};
|
||||||
|
|
||||||
_altUrlController.text = spi.alterUrl ?? '';
|
_altUrlController.text = spi.alterUrl ?? '';
|
||||||
_autoConnect.value = spi.autoConnect ?? true;
|
_autoConnect.value = spi.autoConnect ?? true;
|
||||||
|
|||||||
@@ -1251,7 +1251,8 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final ctrl = TextEditingController(text: val);
|
final ctrl = TextEditingController(text: val);
|
||||||
void onSave(String s) {
|
void onSave() {
|
||||||
|
final s = ctrl.text.trim();
|
||||||
_setting.sftpEditor.put(s);
|
_setting.sftpEditor.put(s);
|
||||||
context.pop();
|
context.pop();
|
||||||
}
|
}
|
||||||
@@ -1265,9 +1266,9 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
hint: '\$EDITOR / vim / nano ...',
|
hint: '\$EDITOR / vim / nano ...',
|
||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
suggestion: false,
|
suggestion: false,
|
||||||
onSubmitted: onSave,
|
onSubmitted: (_) => onSave(),
|
||||||
),
|
),
|
||||||
actions: Btns.oks(onTap: () => onSave(ctrl.text)),
|
actions: [Btn.ok(onTap: (_) => onSave())],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import 'package:fl_lib/fl_lib.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
import 'package:server_box/data/model/server/snippet.dart';
|
||||||
import 'package:server_box/data/res/provider.dart';
|
import 'package:server_box/data/res/provider.dart';
|
||||||
|
|
||||||
import '../../../data/model/server/snippet.dart';
|
|
||||||
|
|
||||||
class SnippetEditPage extends StatefulWidget {
|
class SnippetEditPage extends StatefulWidget {
|
||||||
const SnippetEditPage({super.key, this.snippet});
|
const SnippetEditPage({super.key, this.snippet});
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
final _noteController = TextEditingController();
|
final _noteController = TextEditingController();
|
||||||
final _scriptNode = FocusNode();
|
final _scriptNode = FocusNode();
|
||||||
final _autoRunOn = ValueNotifier(<String>[]);
|
final _autoRunOn = ValueNotifier(<String>[]);
|
||||||
final _tags = ValueNotifier(<String>[]);
|
final _tags = <String>{}.vn;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -36,7 +35,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: CustomAppBar(
|
appBar: CustomAppBar(
|
||||||
title: Text(l10n.edit, style: UIs.text18),
|
title: Text(l10n.edit),
|
||||||
actions: _buildAppBarActions(),
|
actions: _buildAppBarActions(),
|
||||||
),
|
),
|
||||||
body: _buildBody(),
|
body: _buildBody(),
|
||||||
@@ -45,9 +44,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget>? _buildAppBarActions() {
|
List<Widget>? _buildAppBarActions() {
|
||||||
if (widget.snippet == null) {
|
if (widget.snippet == null) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [
|
return [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -89,7 +86,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
final snippet = Snippet(
|
final snippet = Snippet(
|
||||||
name: name,
|
name: name,
|
||||||
script: script,
|
script: script,
|
||||||
tags: _tags.value.isEmpty ? null : _tags.value,
|
tags: _tags.value.isEmpty ? null : _tags.value.toList(),
|
||||||
note: note.isEmpty ? null : note,
|
note: note.isEmpty ? null : note,
|
||||||
autoRunOn: _autoRunOn.value.isEmpty ? null : _autoRunOn.value,
|
autoRunOn: _autoRunOn.value.isEmpty ? null : _autoRunOn.value,
|
||||||
);
|
);
|
||||||
@@ -125,21 +122,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
icon: Icons.note,
|
icon: Icons.note,
|
||||||
suggestion: true,
|
suggestion: true,
|
||||||
),
|
),
|
||||||
ValBuilder(
|
TagTile(tags: _tags, allTags: Pros.snippet.tags.value).cardx,
|
||||||
listenable: _tags,
|
|
||||||
builder: (vals) {
|
|
||||||
return TagEditor(
|
|
||||||
tags: _tags.value,
|
|
||||||
onChanged: (p0) => setState(() {
|
|
||||||
_tags.value = p0;
|
|
||||||
}),
|
|
||||||
allTags: [...Pros.snippet.tags.value],
|
|
||||||
onRenameTag: (old, n) => setState(() {
|
|
||||||
Pros.snippet.renameTag(old, n);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Input(
|
Input(
|
||||||
controller: _scriptController,
|
controller: _scriptController,
|
||||||
node: _scriptNode,
|
node: _scriptNode,
|
||||||
@@ -167,7 +150,10 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
.map((e) => Pros.server.pick(id: e)?.spi.name ?? e)
|
.map((e) => Pros.server.pick(id: e)?.spi.name ?? e)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: const Icon(Icons.settings_remote, size: 19),
|
leading: const Padding(
|
||||||
|
padding: EdgeInsets.only(left: 5),
|
||||||
|
child: Icon(Icons.settings_remote, size: 19),
|
||||||
|
),
|
||||||
title: Text(l10n.autoRun),
|
title: Text(l10n.autoRun),
|
||||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
subtitle: subtitle == null
|
subtitle: subtitle == null
|
||||||
@@ -232,7 +218,7 @@ ${l10n.forExample}:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (snippet.tags != null) {
|
if (snippet.tags != null) {
|
||||||
_tags.value = snippet.tags!;
|
_tags.value = snippet.tags!.toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snippet.autoRunOn != null) {
|
if (snippet.autoRunOn != null) {
|
||||||
|
|||||||
@@ -100,16 +100,8 @@ class _SnippetListPageState extends State<SnippetListPage> {
|
|||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
style: UIs.textGrey,
|
style: UIs.textGrey,
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
mainAxisSize: MainAxisSize.min,
|
onTap: () => AppRoutes.snippetEdit(snippet: snippet).go(context),
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () =>
|
|
||||||
AppRoutes.snippetEdit(snippet: snippet).go(context),
|
|
||||||
icon: const Icon(Icons.edit),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
var newPerm = perm.copyWith();
|
var newPerm = perm.copyWith();
|
||||||
final ok = await context.showRoundDialog(
|
final ok = await context.showRoundDialog(
|
||||||
child: UnixPermEditor(perm: perm, onChanged: (p) => newPerm = p),
|
child: UnixPermEditor(perm: perm, onChanged: (p) => newPerm = p),
|
||||||
actions: Btns.oks(onTap: () => context.pop(true)),
|
actions: [Btn.ok(onTap: (context) => context.pop(true))],
|
||||||
);
|
);
|
||||||
|
|
||||||
final permStr = newPerm.perm;
|
final permStr = newPerm.perm;
|
||||||
@@ -752,7 +752,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
context.showRoundDialog(
|
context.showRoundDialog(
|
||||||
title: l10n.error,
|
title: l10n.error,
|
||||||
child: Text('Unsupport file: ${name.filename}'),
|
child: Text('Unsupport file: ${name.filename}'),
|
||||||
actions: Btns.oks(onTap: () => context.pop()),
|
actions: [Btn.ok()],
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -760,11 +760,10 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
|||||||
final confirm = await context.showRoundDialog(
|
final confirm = await context.showRoundDialog(
|
||||||
title: l10n.attention,
|
title: l10n.attention,
|
||||||
child: SimpleMarkdown(data: '```sh\n$cmd\n```'),
|
child: SimpleMarkdown(data: '```sh\n$cmd\n```'),
|
||||||
actions: Btns.okCancels(
|
actions: [
|
||||||
onTapOk: () => context.pop(true),
|
Btn.cancel(onTap: (c) => c.pop(false)),
|
||||||
onTapCancel: () => context.pop(false),
|
Btn.ok(onTap: (c) => c.pop(true), red: true),
|
||||||
red: true,
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (confirm != true) return;
|
if (confirm != true) return;
|
||||||
|
|
||||||
|
|||||||
@@ -385,8 +385,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "v1.0.90"
|
ref: "v1.0.94"
|
||||||
resolved-ref: "9f17bfce9f6cc9e071c40648bcac8d94e2dee919"
|
resolved-ref: b4e431b7131605fa266be80a016165562b5437eb
|
||||||
url: "https://github.com/lppcg/fl_lib"
|
url: "https://github.com/lppcg/fl_lib"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ dependencies:
|
|||||||
fl_lib:
|
fl_lib:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/lppcg/fl_lib
|
url: https://github.com/lppcg/fl_lib
|
||||||
ref: v1.0.90
|
ref: v1.0.94
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
# dartssh2:
|
# dartssh2:
|
||||||
|
|||||||
Reference in New Issue
Block a user