fix: tag changed without saving

This commit is contained in:
lollipopkit
2023-09-06 20:54:17 +08:00
parent f3970b9fb2
commit 806a223e00
7 changed files with 143 additions and 103 deletions

View File

@@ -14,6 +14,19 @@ class PrivateKeyInfo {
required this.key, required this.key,
}); });
String? get type {
final lines = key.split('\n');
if (lines.length < 2) {
return null;
}
final firstLine = lines[0];
final splited = firstLine.split(RegExp(r'\s+'));
if (splited.length < 2) {
return null;
}
return splited[1];
}
PrivateKeyInfo.fromJson(Map<String, dynamic> json) PrivateKeyInfo.fromJson(Map<String, dynamic> json)
: id = json["id"].toString(), : id = json["id"].toString(),
key = json["private_key"].toString(); key = json["private_key"].toString();

View File

@@ -6,10 +6,13 @@ import '../model/app/net_view.dart';
import '../res/default.dart'; import '../res/default.dart';
class SettingStore extends PersistentStore { class SettingStore extends PersistentStore {
/// Convert all settings into json
Map<String, dynamic> toJson() => {for (var e in box.keys) e: box.get(e)};
// ------BEGIN------ // ------BEGIN------
// These settings are not displayed in the settings page // These settings are not displayed in the settings page
// You can edit them in the settings json editor (by long press the settings // You can edit them in the settings json editor (by long press the settings
// item in the drawer of the server tab page) // item in the drawer of the home page)
/// Discussion #146 /// Discussion #146
late final serverTabUseOldUI = StoreProperty( late final serverTabUseOldUI = StoreProperty(
@@ -31,10 +34,16 @@ class SettingStore extends PersistentStore {
'recordHistory', 'recordHistory',
true, true,
); );
// ------END------
/// Convert all settings into json /// Bigger for bigger font size
Map<String, dynamic> toJson() => {for (var e in box.keys) e: box.get(e)}; /// 1.0 means 100%
/// Warning: This may cause some UI issues
late final textFactor = StoreProperty(
box,
'textFactor',
1.0,
);
// ------END------
late final primaryColor = StoreProperty( late final primaryColor = StoreProperty(
box, box,

View File

@@ -1,6 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:after_layout/after_layout.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -30,8 +29,7 @@ class PrivateKeyEditPage extends StatefulWidget {
_PrivateKeyEditPageState createState() => _PrivateKeyEditPageState(); _PrivateKeyEditPageState createState() => _PrivateKeyEditPageState();
} }
class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
with AfterLayoutMixin {
final _nameController = TextEditingController(); final _nameController = TextEditingController();
final _keyController = TextEditingController(); final _keyController = TextEditingController();
final _pwdController = TextEditingController(); final _pwdController = TextEditingController();
@@ -40,7 +38,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
final _pwdNode = FocusNode(); final _pwdNode = FocusNode();
late FocusScopeNode _focusScope; late FocusScopeNode _focusScope;
late PrivateKeyProvider _provider; final _provider = locator<PrivateKeyProvider>();
late S _s; late S _s;
Widget? _loading; Widget? _loading;
@@ -48,7 +46,18 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_provider = locator<PrivateKeyProvider>(); if (widget.pki != null) {
_nameController.text = widget.pki!.id;
_keyController.text = widget.pki!.key;
} else {
Clipboard.getData(_format).then((value) {
if (value == null) return;
final clipdata = value.text?.trim() ?? '';
if (clipdata.startsWith('-----BEGIN') && clipdata.endsWith('-----')) {
_keyController.text = clipdata;
}
});
}
} }
@override @override
@@ -216,17 +225,4 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
], ],
); );
} }
@override
Future<void> afterFirstLayout(BuildContext context) async {
if (widget.pki != null) {
_nameController.text = widget.pki!.id;
_keyController.text = widget.pki!.key;
} else {
final clipdata = ((await Clipboard.getData(_format))?.text ?? '').trim();
if (clipdata.startsWith('-----BEGIN') && clipdata.endsWith('-----')) {
_keyController.text = clipdata;
}
}
}
} }

View File

@@ -60,14 +60,20 @@ class _PrivateKeyListState extends State<PrivateKeysListPage>
padding: const EdgeInsets.all(13), padding: const EdgeInsets.all(13),
itemCount: key.pkis.length, itemCount: key.pkis.length,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final item = key.pkis[idx];
return RoundRectCard( return RoundRectCard(
ListTile( ListTile(
title: Text(key.pkis[idx].id), leading: Text(
trailing: TextButton( '#$idx',
onPressed: () => style: const TextStyle(
AppRoute.keyEdit(pki: key.pkis[idx]).go(context), fontSize: 15,
child: Text(_s.edit), fontWeight: FontWeight.bold,
),
), ),
title: Text(item.id),
subtitle: Text(item.type ?? _s.unknown, style: grey),
onTap: () => AppRoute.keyEdit(pki: item).go(context),
trailing: const Icon(Icons.edit),
), ),
); );
}, },

View File

@@ -1,11 +1,7 @@
import 'package:after_layout/after_layout.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/context.dart';
import 'package:toolbox/view/widget/input_field.dart';
import 'package:toolbox/view/widget/round_rect_card.dart';
import 'package:toolbox/view/widget/value_notifier.dart';
import '../../../core/route.dart'; import '../../../core/route.dart';
import '../../../core/utils/ui.dart'; import '../../../core/utils/ui.dart';
@@ -16,7 +12,10 @@ import '../../../data/provider/server.dart';
import '../../../data/res/ui.dart'; import '../../../data/res/ui.dart';
import '../../../locator.dart'; import '../../../locator.dart';
import '../../widget/custom_appbar.dart'; import '../../widget/custom_appbar.dart';
import '../../widget/input_field.dart';
import '../../widget/round_rect_card.dart';
import '../../widget/tag.dart'; import '../../widget/tag.dart';
import '../../widget/value_notifier.dart';
class ServerEditPage extends StatefulWidget { class ServerEditPage extends StatefulWidget {
const ServerEditPage({Key? key, this.spi}) : super(key: key); const ServerEditPage({Key? key, this.spi}) : super(key: key);
@@ -27,7 +26,7 @@ class ServerEditPage extends StatefulWidget {
_ServerEditPageState createState() => _ServerEditPageState(); _ServerEditPageState createState() => _ServerEditPageState();
} }
class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin { class _ServerEditPageState extends State<ServerEditPage> {
final _nameController = TextEditingController(); final _nameController = TextEditingController();
final _ipController = TextEditingController(); final _ipController = TextEditingController();
final _altUrlController = TextEditingController(); final _altUrlController = TextEditingController();
@@ -43,12 +42,42 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
late FocusScopeNode _focusScope; late FocusScopeNode _focusScope;
late S _s; late S _s;
final _serverProvider = locator<ServerProvider>(); final _srvs = locator<ServerProvider>();
final _keyProvider = locator<PrivateKeyProvider>(); final _keys = locator<PrivateKeyProvider>();
final _keyIdx = ValueNotifier<int?>(null); final _keyIdx = ValueNotifier<int?>(null);
final _autoConnect = ValueNotifier(true); final _autoConnect = ValueNotifier(true);
List<String> _tags = <String>[];
var _tags = <String>[];
@override
void initState() {
super.initState();
if (widget.spi != null) {
_nameController.text = widget.spi?.name ?? '';
_ipController.text = widget.spi?.ip ?? '';
_portController.text = (widget.spi?.port ?? 22).toString();
_usernameController.text = widget.spi?.user ?? '';
if (widget.spi?.pubKeyId == null) {
_passwordController.text = widget.spi?.pwd ?? '';
} else {
_keyIdx.value = _keys.pkis.indexWhere(
(e) => e.id == widget.spi!.pubKeyId,
);
}
if (widget.spi?.tags != null) {
/// List in dart is passed by pointer, so you need to copy it here
_tags.addAll(widget.spi!.tags!);
}
if (widget.spi?.alterUrl != null) {
_altUrlController.text = widget.spi!.alterUrl!;
}
if (widget.spi?.autoConnect != null) {
_autoConnect.value = widget.spi!.autoConnect!;
}
}
}
@override @override
void dispose() { void dispose() {
@@ -92,7 +121,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
_serverProvider.delServer(widget.spi!.id); _srvs.delServer(widget.spi!.id);
context.pop(); context.pop();
context.pop(true); context.pop(true);
}, },
@@ -159,14 +188,10 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
), ),
TagEditor( TagEditor(
tags: _tags, tags: _tags,
onChanged: (p0) { onChanged: (p0) => _tags = p0,
setState(() {
_tags = p0;
});
},
s: _s, s: _s,
tagSuggestions: [..._serverProvider.tags], allTags: [..._srvs.tags],
onRenameTag: _serverProvider.renameTag, onRenameTag: _srvs.renameTag,
), ),
_buildAuth(), _buildAuth(),
ListTile( ListTile(
@@ -240,7 +265,16 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
final e = key.pkis[index]; final e = key.pkis[index];
return ListTile( return ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
leading: Text(
'#${index + 1}',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
title: Text(e.id, textAlign: TextAlign.start), title: Text(e.id, textAlign: TextAlign.start),
subtitle: Text(
e.type ?? _s.unknown,
textAlign: TextAlign.start,
style: grey,
),
trailing: _buildRadio(index, e), trailing: _buildRadio(index, e),
); );
}); });
@@ -287,28 +321,6 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
); );
} }
@override
void afterFirstLayout(BuildContext context) {
if (widget.spi != null) {
_nameController.text = widget.spi?.name ?? '';
_ipController.text = widget.spi?.ip ?? '';
_portController.text = (widget.spi?.port ?? 22).toString();
_usernameController.text = widget.spi?.user ?? '';
if (widget.spi?.pubKeyId == null) {
_passwordController.text = widget.spi?.pwd ?? '';
} else {
_keyIdx.value =
_keyProvider.pkis.indexWhere((e) => e.id == widget.spi!.pubKeyId);
}
if (widget.spi?.tags != null) {
_tags = widget.spi!.tags!;
}
_altUrlController.text = widget.spi?.alterUrl ?? '';
_autoConnect.value = widget.spi?.autoConnect ?? true;
setState(() {});
}
}
void _onSave() async { void _onSave() async {
if (_ipController.text == '') { if (_ipController.text == '') {
showSnackBar(context, Text(_s.plzEnterHost)); showSnackBar(context, Text(_s.plzEnterHost));
@@ -353,7 +365,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
user: _usernameController.text, user: _usernameController.text,
pwd: _passwordController.text.isEmpty ? null : _passwordController.text, pwd: _passwordController.text.isEmpty ? null : _passwordController.text,
pubKeyId: _keyIdx.value != null pubKeyId: _keyIdx.value != null
? _keyProvider.pkis.elementAt(_keyIdx.value!).id ? _keys.pkis.elementAt(_keyIdx.value!).id
: null, : null,
tags: _tags, tags: _tags,
alterUrl: _altUrlController.text.isEmpty ? null : _altUrlController.text, alterUrl: _altUrlController.text.isEmpty ? null : _altUrlController.text,
@@ -361,9 +373,9 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
); );
if (widget.spi == null) { if (widget.spi == null) {
_serverProvider.addServer(spi); _srvs.addServer(spi);
} else { } else {
_serverProvider.updateServer(widget.spi!, spi); _srvs.updateServer(widget.spi!, spi);
} }
context.pop(); context.pop();

View File

@@ -129,7 +129,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
_tags = p0; _tags = p0;
}), }),
s: _s, s: _s,
tagSuggestions: [..._provider.tags], allTags: [..._provider.tags],
onRenameTag: (old, n) => setState(() { onRenameTag: (old, n) => setState(() {
_provider.renameTag(old, n); _provider.renameTag(old, n);
}), }),

View File

@@ -37,12 +37,12 @@ class TagBtn extends StatelessWidget {
} }
} }
class TagEditor extends StatelessWidget { class TagEditor extends StatefulWidget {
final List<String> tags; final List<String> tags;
final S s; final S s;
final void Function(List<String>)? onChanged; final void Function(List<String>)? onChanged;
final void Function(String old, String new_)? onRenameTag; final void Function(String old, String new_)? onRenameTag;
final List<String>? tagSuggestions; final List<String> allTags;
const TagEditor({ const TagEditor({
super.key, super.key,
@@ -50,40 +50,46 @@ class TagEditor extends StatelessWidget {
required this.s, required this.s,
this.onChanged, this.onChanged,
this.onRenameTag, this.onRenameTag,
this.tagSuggestions, this.allTags = const <String>[],
}); });
@override
State<StatefulWidget> createState() => _TagEditorState();
}
class _TagEditorState extends State<TagEditor> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RoundRectCard(ListTile( return RoundRectCard(ListTile(
leading: const Icon(Icons.tag), leading: const Icon(Icons.tag),
title: _buildTags(context, tags), title: _buildTags(widget.tags),
trailing: InkWell( trailing: InkWell(
child: const Icon(Icons.add), child: const Icon(Icons.add),
onTap: () { onTap: () {
_showAddTagDialog(context, tags, onChanged); _showAddTagDialog();
}, },
), ),
)); ));
} }
Widget _buildTags(BuildContext context, List<String> tags) { Widget _buildTags(List<String> tags) {
tagSuggestions?.removeWhere((element) => tags.contains(element)); final suggestions = widget.allTags.where((e) => !tags.contains(e)).toList();
final suggestionLen = tagSuggestions?.length ?? 0; final suggestionLen = suggestions.length;
/// Add vertical divider if suggestions.length > 0
final counts = tags.length + suggestionLen + (suggestionLen == 0 ? 0 : 1); final counts = tags.length + suggestionLen + (suggestionLen == 0 ? 0 : 1);
if (counts == 0) return Text(s.tag); if (counts == 0) return Text(widget.s.tag);
return ConstrainedBox( return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: _kTagBtnHeight), constraints: const BoxConstraints(maxHeight: _kTagBtnHeight),
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index < tags.length) { if (index < tags.length) {
return _buildTagItem(context, tags[index], false); return _buildTagItem(tags[index]);
} else if (index > tags.length) { } else if (index > tags.length) {
return _buildTagItem( return _buildTagItem(
context, suggestions[index - tags.length - 1],
tagSuggestions![index - tags.length - 1], isAdd: true,
true,
); );
} }
return const VerticalDivider(); return const VerticalDivider();
@@ -93,7 +99,7 @@ class TagEditor extends StatelessWidget {
); );
} }
Widget _buildTagItem(BuildContext context, String tag, bool isAdd) { Widget _buildTagItem(String tag, {bool isAdd = false}) {
return _wrap( return _wrap(
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -114,65 +120,63 @@ class TagEditor extends StatelessWidget {
), ),
onTap: () { onTap: () {
if (isAdd) { if (isAdd) {
tags.add(tag); widget.tags.add(tag);
} else { } else {
tags.remove(tag); widget.tags.remove(tag);
} }
onChanged?.call(tags); widget.onChanged?.call(widget.tags);
setState(() {});
}, },
onLongPress: () => _showRenameDialog(context, tag), onLongPress: () => _showRenameDialog(tag),
); );
} }
void _showAddTagDialog( void _showAddTagDialog() {
BuildContext context,
List<String> tags,
void Function(List<String>)? onChanged,
) {
final textEditingController = TextEditingController(); final textEditingController = TextEditingController();
showRoundDialog( showRoundDialog(
context: context, context: context,
title: Text(s.add), title: Text(widget.s.add),
child: Input( child: Input(
autoFocus: true, autoFocus: true,
icon: Icons.tag, icon: Icons.tag,
controller: textEditingController, controller: textEditingController,
hint: s.tag, hint: widget.s.tag,
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
final tag = textEditingController.text; final tag = textEditingController.text;
tags.add(tag.trim()); widget.tags.add(tag.trim());
onChanged?.call(tags); widget.onChanged?.call(widget.tags);
Navigator.pop(context); context.pop();
}, },
child: Text(s.add), child: Text(widget.s.add),
), ),
], ],
); );
} }
void _showRenameDialog(BuildContext context, String tag) { void _showRenameDialog(String tag) {
final textEditingController = TextEditingController(text: tag); final textEditingController = TextEditingController(text: tag);
showRoundDialog( showRoundDialog(
context: context, context: context,
title: Text(s.rename), title: Text(widget.s.rename),
child: Input( child: Input(
autoFocus: true, autoFocus: true,
icon: Icons.abc, icon: Icons.abc,
controller: textEditingController, controller: textEditingController,
hint: s.tag, hint: widget.s.tag,
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
final newTag = textEditingController.text.trim(); final newTag = textEditingController.text.trim();
if (newTag.isEmpty) return; if (newTag.isEmpty) return;
onRenameTag?.call(tag, newTag); widget.onRenameTag?.call(tag, newTag);
context.pop(); context.pop();
setState(() {});
}, },
child: Text(s.rename), child: Text(widget.s.rename),
), ),
], ],
); );