Mã nguồn đã công bố
Tải mã nguồn dạng ZIP
CH-J SM Mobile
Duyệt thư mục và tệp của một bản phát hành ứng dụng cụ thể.
CH-J / SM Mobile
175 tệp
sm-mobile-20261016T185235Z-8e93ecc1b59a
Ngày thêm: · Ngày dựng:
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
isExpanded: true,131
items: AuthMethod.values132
.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
}192
}SHA-256
45f45bd0e1784790642086ecc5ec6d82b1bc4180351d1680a26f54f0937d7e25CH-J Proprietary Software License 1.14 · Mã nguồn được cung cấp theo CH-J Proprietary Software License 1.14. Việc công khai mã nguồn không thay đổi các điều khoản giấy phép và không cấp thêm quyền nào.
SHA-256 của tệp nén
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655