Fuente publicada
Descargar fuente ZIP
CH-J SM Mobile
Busque directorios y archivos para obtener una versión de aplicación específica.
CH-J / SM Mobile
134 archivos
sm-mobile-20261016T050549Z-3678f2a05fa9
Añadido: · Construido:
1
import 'dart:convert';2
import 'package:file_picker/file_picker.dart';3
import 'package:flutter/material.dart';4
import '../core/app_controller.dart';5
import '../core/failure.dart';6
import '../l10n/strings.dart';7
import '../models/server_profile.dart';8
import '../services/local_files.dart';9
import 'common.dart';11
class ProfileEditor extends StatefulWidget {12
final AppController controller;13
final ServerProfile? profile;14
const ProfileEditor(this.controller, {this.profile, super.key});15
@override16
State<ProfileEditor> createState() => _ProfileEditorState();17
}19
class _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 || clearPassword68
? ''69
: savePassword70
? password.text71
: 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
@override87
void dispose() {88
for (final field in [name, host, username, port, password]) {89
field.dispose();90
}91
key = null;92
super.dispose();93
}95
@override96
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
items: AuthMethod.values131
.map(132
(a) => DropdownMenuItem(133
value: a,134
child: Text(context.tr(a.name)),135
),136
)137
.toList(),138
onChanged: (a) => setState(() => auth = a!),139
),140
const SizedBox(height: 16),141
if (auth == AuthMethod.privateKey) ...[142
OutlinedButton.icon(143
onPressed: chooseKey,144
icon: const Icon(Icons.key),145
label: Text(context.tr('chooseKey')),146
),147
if (key != null ||148
(widget.profile != null &&149
widget.controller.vault.secret(150
widget.profile!.id,151
'privateKey',152
) !=153
null))154
Text(context.tr('keyStored')),155
] else ...[156
CheckboxListTile(157
value: savePassword,158
onChanged: (v) => setState(() => savePassword = v!),159
title: Text(context.tr('savePassword')),160
),161
if (savePassword)162
TextField(163
controller: password,164
obscureText: true,165
autocorrect: false,166
enableSuggestions: false,167
decoration: InputDecoration(168
labelText: context.tr('password'),169
),170
),171
if (widget.profile != null)172
CheckboxListTile(173
value: clearPassword,174
onChanged: (v) => setState(() => clearPassword = v!),175
title: Text(context.tr('clearPassword')),176
),177
],178
const SizedBox(height: 20),179
FilledButton(180
onPressed: busy ? null : save,181
child: Text(context.tr('save')),182
),183
if (busy) const LinearProgressIndicator(),184
],185
),186
),187
),188
),189
);190
}191
}SHA-256
0f8a63df1421e14e9080f0a849b7b2f0523340f933eb9f732d518e5b9cff0292CH-J Proprietary Software License 1.14 · La fuente se proporciona en el CH-J Proprietary Software License 1.14. Su disponibilidad no cambia los términos de la licencia ni otorga derechos adicionales.
Archivo SHA-256
c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65