CH-J SM Mobilespráva serverů přes SSH
Menu
Vydané zdroje

CH-J SM Mobile

Procházejte adresáře a soubory konkrétního vydání aplikace.

Stáhnout zdroje ZIP
CH-J / SM Mobile 175 souborů
sm-mobile-20261016T185235Z-8e93ecc1b59a Přidáno: · Sestaveno:
profile_editor.dart Přidáno: Datum není evidováno 6.4 KB · 192 řádků
Kód Stáhnout soubor
1import 'dart:convert';
2import 'package:file_picker/file_picker.dart';
3import 'package:flutter/material.dart';
4import '../core/app_controller.dart';
5import '../core/failure.dart';
6import '../l10n/strings.dart';
7import '../models/server_profile.dart';
8import '../services/local_files.dart';
9import 'common.dart';
11class ProfileEditor extends StatefulWidget {
12 final AppController controller;
13 final ServerProfile? profile;
14 const ProfileEditor(this.controller, {this.profile, super.key});
15 @override
16 State<ProfileEditor> createState() => _ProfileEditorState();
19class _ProfileEditorState extends State<ProfileEditor> {
20 late final name = TextEditingController(text: widget.profile?.name);
21 late final host = TextEditingController(text: widget.profile?.host);
22 late final username = TextEditingController(text: widget.profile?.username);
23 late final port = TextEditingController(
24 text: '${widget.profile?.port ?? 22}',
25 );
26 final password = TextEditingController();
27 late AuthMethod auth = widget.profile?.auth ?? AuthMethod.password;
28 String? key;
29 bool savePassword = false, clearPassword = false, busy = false;
30 Future<void> chooseKey() async {
31 try {
32 final picked = await widget.controller.withSystemDialog(
33 () => FilePicker.pickFile(),
34 );
35 if (picked == null || !mounted || !widget.controller.vault.unlocked) {
36 return;
37 }
38 final text = utf8.decode(
39 await readBounded(picked.readAsByteStream(), 1024 * 1024),
40 );
41 if (!text.contains('PRIVATE KEY')) throw const Failure('invalidKey');
42 if (mounted) setState(() => key = text);
43 } catch (e) {
44 if (mounted) showFailure(context, e);
45 }
46 }
48 Future<void> save() async {
49 setState(() => busy = true);
50 try {
51 final c = widget.controller;
52 final profile = ServerProfile(
53 id: widget.profile?.id,
54 name: name.text,
55 host: host.text,
56 username: username.text,
57 port: int.tryParse(port.text) ?? 0,
58 auth: auth,
59 );
60 if (auth == AuthMethod.privateKey &&
61 key == null &&
62 c.vault.secret(profile.id, 'privateKey') == null) {
63 throw const Failure('invalidKey');
64 }
65 await c.vault.save(
66 profile,
67 password: auth == AuthMethod.privateKey || clearPassword
68 ? ''
69 : savePassword
70 ? password.text
71 : null,
72 privateKey: auth == AuthMethod.password ? '' : key,
73 );
74 password.clear();
75 key = null;
76 if (c.ssh.profile?.id == profile.id) c.ssh.disconnect(clear: true);
77 c.changed();
78 if (mounted) Navigator.pop(context);
79 } catch (e) {
80 if (mounted) showFailure(context, e);
81 } finally {
82 if (mounted) setState(() => busy = false);
83 }
84 }
86 @override
87 void dispose() {
88 for (final field in [name, host, username, port, password]) {
89 field.dispose();
90 }
91 key = null;
92 super.dispose();
93 }
95 @override
96 Widget build(BuildContext context) {
97 Widget field(
98 TextEditingController controller,
99 String label, {
100 TextInputType? type,
101 }) => Padding(
102 padding: const EdgeInsets.only(bottom: 16),
103 child: TextField(
104 controller: controller,
105 keyboardType: type,
106 autocorrect: false,
107 enableSuggestions: false,
108 decoration: InputDecoration(labelText: context.tr(label)),
109 ),
110 );
111 return Scaffold(
112 appBar: AppBar(
113 title: Text(
114 context.tr(widget.profile == null ? 'addServer' : 'editServer'),
115 ),
116 ),
117 body: SafeArea(
118 child: Center(
119 child: ConstrainedBox(
120 constraints: const BoxConstraints(maxWidth: 600),
121 child: ListView(
122 padding: const EdgeInsets.all(20),
123 children: [
124 field(name, 'name'),
125 field(host, 'host', type: TextInputType.url),
126 field(username, 'username'),
127 field(port, 'port', type: TextInputType.number),
128 DropdownButtonFormField<AuthMethod>(
129 initialValue: auth,
130 isExpanded: true,
131 items: AuthMethod.values
132 .map(
133 (a) => DropdownMenuItem(
134 value: a,
135 child: Text(context.tr(a.name)),
136 ),
137 )
138 .toList(),
139 onChanged: (a) => setState(() => auth = a!),
140 ),
141 const SizedBox(height: 16),
142 if (auth == AuthMethod.privateKey) ...[
143 OutlinedButton.icon(
144 onPressed: chooseKey,
145 icon: const Icon(Icons.key),
146 label: Text(context.tr('chooseKey')),
147 ),
148 if (key != null ||
149 (widget.profile != null &&
150 widget.controller.vault.secret(
151 widget.profile!.id,
152 'privateKey',
153 ) !=
154 null))
155 Text(context.tr('keyStored')),
156 ] else ...[
157 CheckboxListTile(
158 value: savePassword,
159 onChanged: (v) => setState(() => savePassword = v!),
160 title: Text(context.tr('savePassword')),
161 ),
162 if (savePassword)
163 TextField(
164 controller: password,
165 obscureText: true,
166 autocorrect: false,
167 enableSuggestions: false,
168 decoration: InputDecoration(
169 labelText: context.tr('password'),
170 ),
171 ),
172 if (widget.profile != null)
173 CheckboxListTile(
174 value: clearPassword,
175 onChanged: (v) => setState(() => clearPassword = v!),
176 title: Text(context.tr('clearPassword')),
177 ),
178 ],
179 const SizedBox(height: 20),
180 FilledButton(
181 onPressed: busy ? null : save,
182 child: Text(context.tr('save')),
183 ),
184 if (busy) const LinearProgressIndicator(),
185 ],
186 ),
187 ),
188 ),
189 ),
190 );
191 }
SHA-25645f45bd0e1784790642086ecc5ec6d82b1bc4180351d1680a26f54f0937d7e25

CH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.

SHA-256 archivu46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655
Soubory