شائع شدہ سورس
سورس ZIP ڈاؤن لوڈ کریں
CH-J SM Mobile
ایپلیکیشن کی کسی مخصوص ریلیز کی ڈائریکٹریز اور فائلیں دیکھیں۔
CH-J / SM Mobile
175 فائلیں
sm-mobile-20261016T185235Z-8e93ecc1b59a
شامل کیا گیا: · بلڈ کیا گیا:
1
import 'package:flutter/material.dart';2
import '../core/app_controller.dart';3
import '../l10n/strings.dart';4
import '../models/server_profile.dart';5
import 'adaptive.dart';6
import 'common.dart';7
import 'profile_editor.dart';8
import 'profile_io.dart';10
Future<void> connectProfile(11
BuildContext context,12
AppController c,13
ServerProfile profile,14
) async {15
final password = TextEditingController(),16
passphrase = TextEditingController();17
try {18
final stored = c.vault.secret(19
profile.id,20
profile.auth == AuthMethod.password ? 'password' : 'privateKey',21
);22
final allowed = await showDialog<bool>(23
context: context,24
builder: (ctx) => AlertDialog(25
title: Text('${ctx.tr('connect')} · ${profile.name}'),26
content: SingleChildScrollView(27
child: Column(28
mainAxisSize: MainAxisSize.min,29
children: [30
SelectableText(31
'${profile.username}@${profile.host}:${profile.port}',32
),33
const SizedBox(height: 16),34
if (profile.auth == AuthMethod.password)35
TextField(36
controller: password,37
obscureText: true,38
autocorrect: false,39
enableSuggestions: false,40
decoration: InputDecoration(41
labelText: ctx.tr('password'),42
hintText: stored == null ? null : '••••••••',43
),44
),45
if (profile.auth == AuthMethod.privateKey)46
TextField(47
controller: passphrase,48
obscureText: true,49
autocorrect: false,50
enableSuggestions: false,51
decoration: InputDecoration(labelText: ctx.tr('passphrase')),52
),53
],54
),55
),56
actions: [57
TextButton(58
onPressed: () => Navigator.pop(ctx, false),59
child: Text(ctx.tr('cancel')),60
),61
FilledButton(62
onPressed: () => Navigator.pop(ctx, true),63
child: Text(ctx.tr('connect')),64
),65
],66
),67
);68
if (allowed != true || !c.vault.unlocked || !context.mounted) return;69
final credential = profile.auth == AuthMethod.password70
? (password.text.isEmpty ? stored ?? '' : password.text)71
: stored ?? '';72
await c.ssh.connect(73
profile,74
credential: credential,75
passphrase: passphrase.text.isEmpty ? null : passphrase.text,76
prompt: (endpoint, type, fingerprint) async {77
if (!context.mounted || !c.vault.unlocked) return false;78
return await showDialog<bool>(79
context: context,80
barrierDismissible: false,81
builder: (ctx) => AlertDialog(82
title: Text(ctx.tr('hostTrustTitle')),83
content: SingleChildScrollView(84
child: Column(85
mainAxisSize: MainAxisSize.min,86
crossAxisAlignment: CrossAxisAlignment.start,87
children: [88
Text(ctx.tr('hostTrustBody')),89
const SizedBox(height: 16),90
SelectableText('$endpoint\n$type\n$fingerprint'),91
],92
),93
),94
actions: [95
TextButton(96
onPressed: () => Navigator.pop(ctx, false),97
child: Text(ctx.tr('cancel')),98
),99
FilledButton(100
onPressed: () => Navigator.pop(ctx, true),101
child: Text(ctx.tr('trust')),102
),103
],104
),105
) ??106
false;107
},108
);109
} catch (e) {110
if (context.mounted) showFailure(context, e);111
} finally {112
password.clear();113
passphrase.clear();114
Future.delayed(const Duration(seconds: 1), () {115
password.dispose();116
passphrase.dispose();117
});118
}119
}121
class ServersPage extends StatefulWidget {122
final AppController controller;123
final VoidCallback onConnected;124
// Owned by the caller when the search must outlive this page, for example125
// when a fold hides the list and shows it again.126
final TextEditingController? search;127
const ServersPage(128
this.controller, {129
required this.onConnected,130
this.search,131
super.key,132
});133
@override134
State<ServersPage> createState() => _ServersPageState();135
}137
class _ServersPageState extends State<ServersPage> {138
late final search = widget.search ?? TextEditingController();139
@override140
void dispose() {141
if (widget.search == null) search.dispose();142
super.dispose();143
}145
@override146
Widget build(BuildContext context) {147
final c = widget.controller;148
final query = search.text;149
final profiles = c.vault.profiles150
.where(151
(p) => '${p.name} ${p.host} ${p.username}'.toLowerCase().contains(152
query.toLowerCase(),153
),154
)155
.toList();156
return LayoutBuilder(157
builder: (context, constraints) {158
final pad = readablePadding(constraints, maxWidth: 900, gutter: 16);159
return MinHeight(160
minHeight: 200,161
child: Column(162
children: [163
Padding(164
padding: pad,165
child: Row(166
children: [167
Expanded(168
child: TextField(169
controller: search,170
decoration: InputDecoration(171
labelText: context.tr('search'),172
prefixIcon: const Icon(Icons.search),173
),174
onChanged: (_) => setState(() {}),175
),176
),177
const SizedBox(width: 10),178
IconButton.filled(179
onPressed: () => Navigator.push(180
context,181
MaterialPageRoute(builder: (_) => ProfileEditor(c)),182
),183
tooltip: context.tr('addServer'),184
icon: const Icon(Icons.add),185
),186
// Desktop-compatible JSON, also available in Settings.187
PopupMenuButton<String>(188
tooltip: context.tr('profileTransfer'),189
icon: const Icon(Icons.import_export),190
onSelected: (action) async {191
try {192
if (action == 'importProfiles') {193
await importProfiles(context, c);194
} else {195
await exportProfiles(c);196
}197
} catch (e) {198
if (context.mounted) showFailure(context, e);199
}200
},201
itemBuilder: (ctx) => [202
for (final (value, icon) in [203
('importProfiles', Icons.file_open),204
('exportProfiles', Icons.ios_share),205
])206
PopupMenuItem(207
value: value,208
child: ListTile(209
leading: Icon(icon),210
title: Text(ctx.tr(value)),211
contentPadding: EdgeInsets.zero,212
),213
),214
],215
),216
],217
),218
),219
Expanded(220
child: profiles.isEmpty221
? const PageMessage('emptyServers')222
: ListView.builder(223
padding: pad.copyWith(top: 0),224
itemCount: profiles.length,225
itemBuilder: (context, i) {226
final p = profiles[i];227
final active =228
c.ssh.profile?.id == p.id &&229
(c.ssh.connected || c.ssh.connecting);230
return Card(231
child: ListTile(232
leading: Icon(233
Icons.dns_outlined,234
color: active235
? const Color(0xff43d6a0)236
: Theme.of(context).colorScheme.primary,237
),238
title: Text(p.name),239
subtitle: Text(240
'${p.username}@${p.host}:${p.port}\n${context.tr(active241
? c.ssh.connecting242
? 'connecting'243
: 'connected'244
: 'disconnected')}',245
),246
isThreeLine: true,247
onTap: c.ssh.connecting248
? null249
: () async {250
await connectProfile(context, c, p);251
if (c.ssh.connected) widget.onConnected();252
},253
trailing: PopupMenuButton<String>(254
onSelected: (action) async {255
try {256
if (action == 'edit') {257
await Navigator.push(258
context,259
MaterialPageRoute(260
builder: (_) =>261
ProfileEditor(c, profile: p),262
),263
);264
}265
if (action == 'duplicate') {266
await c.vault.save(267
ServerProfile(268
name: p.name,269
host: p.host,270
port: p.port,271
username: p.username,272
),273
);274
c.changed();275
}276
if (action == 'delete' &&277
context.mounted &&278
await confirm(279
context,280
'confirmDelete',281
detail: p.name,282
)) {283
if (c.ssh.profile?.id == p.id) {284
c.ssh.disconnect(clear: true);285
}286
await c.vault.deleteProfile(p.id);287
c.changed();288
}289
} catch (e) {290
if (context.mounted) {291
showFailure(context, e);292
}293
}294
},295
itemBuilder: (ctx) =>296
['edit', 'duplicate', 'delete']297
.map(298
(v) => PopupMenuItem(299
value: v,300
child: Text(ctx.tr(v)),301
),302
)303
.toList(),304
),305
),306
);307
},308
),309
),310
],311
),312
);313
},314
);315
}316
}SHA-256
e61002750211c94a3d74cce5c282893840b9fbd2d703d7350f6c673fe004d682CH-J Proprietary Software License 1.14 · سورس CH-J Proprietary Software License 1.14 کے تحت فراہم کیا جاتا ہے۔ اس کی دستیابی لائسنس کی شرائط نہیں بدلتی اور نہ ہی اضافی حقوق دیتی ہے۔
آرکائیو کا SHA-256
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655