Nailathalang source
I-download ang source ZIP
CH-J SM Mobile
I-browse ang mga direktoryo at file ng isang partikular na release ng application.
CH-J / SM Mobile
175 file
sm-mobile-20261016T185235Z-8e93ecc1b59a
Idinagdag: · Binuo:
1
import 'dart:typed_data';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 '../services/profile_transfer.dart';10
import 'adaptive.dart';12
/// Imports desktop-compatible `chj-server-profiles` version 1 JSON after a13
/// preview and a duplicate policy. Credentials are never part of the file.14
Future<void> importProfiles(BuildContext context, AppController c) async {15
final epoch = c.vault.generation;16
final file = await c.withSystemDialog(17
() =>18
FilePicker.pickFile(type: FileType.custom, allowedExtensions: ['json']),19
);20
if (file == null ||21
!context.mounted ||22
epoch != c.vault.generation ||23
!c.vault.unlocked) {24
return;25
}26
final profiles = ProfileTransfer.decode(27
await readBounded(file.readAsByteStream(), ProfileTransfer.maxBytes),28
);29
if (!context.mounted) return;30
final policy = await Navigator.push<String>(31
context,32
MaterialPageRoute(33
builder: (_) => ImportPreview(profiles, c.vault.profiles),34
),35
);36
if (policy == null || !c.vault.unlocked || epoch != c.vault.generation) {37
return;38
}39
await c.vault.change((d) {40
final list = d['profiles'] as List;41
final seen = {42
for (final p in list)43
ServerProfile.fromJson(Map<String, dynamic>.from(p)).identity: p,44
};45
for (final p in profiles) {46
final match = seen[p.identity];47
if (match != null && policy == 'skipDuplicates') continue;48
final next = ServerProfile(49
id: match != null && policy == 'updateDuplicates' ? match['id'] : null,50
name: p.name,51
host: p.host,52
username: p.username,53
port: p.port,54
);55
if (match != null && policy == 'updateDuplicates') {56
list[list.indexOf(match)] = next.toJson();57
(d['secrets'] as Map).remove(next.id);58
} else {59
list.add(next.toJson());60
}61
seen[p.identity] = list.lastWhere((r) => r['id'] == next.id);62
}63
});64
c.ssh.disconnect(clear: true);65
c.changed();66
}68
/// Exports profiles as desktop-compatible JSON, without credentials.69
Future<void> exportProfiles(AppController c) async {70
if (c.vault.profiles.isEmpty) throw const Failure('invalidImport');71
final bytes = Uint8List.fromList(ProfileTransfer.encode(c.vault.profiles));72
await c.withSystemDialog(73
() => FilePicker.saveFile(74
fileName: 'chj-server-profiles.json',75
bytes: bytes,76
mimeType: 'application/json',77
),78
);79
}81
class ImportPreview extends StatefulWidget {82
final List<ServerProfile> profiles, existing;83
const ImportPreview(this.profiles, this.existing, {super.key});84
@override85
State<ImportPreview> createState() => _ImportPreviewState();86
}88
class _ImportPreviewState extends State<ImportPreview> {89
String policy = 'skipDuplicates';90
@override91
Widget build(BuildContext context) {92
final seen = widget.existing.map((p) => p.identity).toSet();93
final duplicates = widget.profiles.map((p) {94
final found = !seen.add(p.identity);95
return found;96
}).toList();97
return Scaffold(98
appBar: AppBar(title: Text(context.tr('importPreview'))),99
body: SafeArea(100
child: MinHeight(101
minHeight: 320,102
child: Column(103
children: [104
Padding(105
padding: const EdgeInsets.all(16),106
child: Text(context.tr('importAdvice')),107
),108
Padding(109
padding: const EdgeInsets.symmetric(horizontal: 16),110
child: DropdownButtonFormField<String>(111
initialValue: policy,112
isExpanded: true,113
items:114
['skipDuplicates', 'updateDuplicates', 'createDuplicates']115
.map(116
(v) => DropdownMenuItem(117
value: v,118
child: Text(119
context.tr(v),120
overflow: TextOverflow.ellipsis,121
),122
),123
)124
.toList(),125
onChanged: (v) => setState(() => policy = v!),126
),127
),128
Expanded(129
child: ListView.builder(130
itemCount: widget.profiles.length,131
itemBuilder: (ctx, i) {132
final p = widget.profiles[i];133
return ListTile(134
title: Text(p.name),135
subtitle: Text('${p.username}@${p.host}:${p.port}'),136
trailing: duplicates[i]137
? Tooltip(138
message: ctx.tr('duplicate'),139
child: const Icon(Icons.copy),140
)141
: null,142
);143
},144
),145
),146
Padding(147
padding: const EdgeInsets.all(16),148
child: FilledButton(149
onPressed: () => Navigator.pop(context, policy),150
child: Text(context.tr('importAction')),151
),152
),153
],154
),155
),156
),157
);158
}159
}SHA-256
2ae0dbdeee1c944ea7134e7ec7a9559788eae3a5de132f8fdeb48e0ab2abfb22CH-J Proprietary Software License 1.14 · Ibinibigay ang source sa ilalim ng CH-J Proprietary Software License 1.14. Hindi binabago ng pagiging available nito ang mga tuntunin ng lisensya at hindi ito nagbibigay ng karagdagang karapatan.
SHA-256 ng archive
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655