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:
save_as.dart Přidáno: Datum není evidováno 4,4 KB · 140 řádků
Kód Stáhnout soubor
1import 'dart:typed_data';
2import 'package:flutter/material.dart';
3import '../../core/app_controller.dart';
4import '../../core/failure.dart';
5import '../../l10n/strings.dart';
6import '../../services/editor/languages.dart';
7import '../../services/editor/recents.dart';
8import '../../services/editor/sources.dart';
9import '../../services/sftp_service.dart';
10import '../common.dart';
12/// Remembers [source] in the encrypted list of recent files.
13Future<void> recordRecent(AppController c, FileSource source) async {
14 if (source.kind == 'new' || !c.vault.unlocked) return;
15 try {
16 await RecentFiles(c.vault).touch(
17 RecentFile(
18 key: source.key,
19 kind: source.kind,
20 name: source.name,
21 location: source.location,
22 source: source.toJson(),
23 opened: DateTime.now(),
24 ),
25 );
26 } catch (_) {
27 /* Recents are a convenience. */
28 }
31/// Saves [bytes] under a new name: on the device (system save dialog) or on
32/// the connected server. Only ever on explicit user choice. Returns the new
33/// source, or null when cancelled.
34Future<FileSource?> saveAsDialog(
35 BuildContext context,
36 AppController c, {
37 required String name,
38 required Uint8List bytes,
39 String? target,
40}) async {
41 final connected = c.ssh.connected;
42 final where =
43 target ??
44 await showModalBottomSheet<String>(
45 context: context,
46 builder: (ctx) => SafeArea(
47 child: Column(
48 mainAxisSize: MainAxisSize.min,
49 children: [
50 ListTile(
51 title: Text(
52 ctx.tr('saveAs'),
53 style: Theme.of(ctx).textTheme.titleMedium,
54 ),
55 ),
56 ListTile(
57 leading: const Icon(Icons.phone_iphone),
58 title: Text(ctx.tr('saveToDevice')),
59 onTap: () => Navigator.pop(ctx, 'device'),
60 ),
61 ListTile(
62 leading: const Icon(Icons.dns_outlined),
63 enabled: connected,
64 title: Text(ctx.tr('saveToServer')),
65 subtitle: Text(
66 connected ? c.ssh.profile!.name : ctx.tr('selectServer'),
67 ),
68 onTap: () => Navigator.pop(ctx, 'server'),
69 ),
70 ],
71 ),
72 ),
73 );
74 if (where == null || !context.mounted) return null;
75 try {
76 if (where == 'device') {
77 final doc = await c.withSystemDialog(
78 () => c.documents.saveAs(name, bytes, mime: mimeOf(name)),
79 );
80 if (doc == null) return null;
81 return DeviceSource.saved(c.documents, doc, bytes.length);
82 }
83 final profile = c.ssh.profile!;
84 final service = await c.sftp();
85 final dir = c.remoteDirectory ?? await service.home();
86 if (!context.mounted) return null;
87 final path = await _askPath(context, childPath(dir, safeName(name)));
88 if (path == null || !context.mounted) return null;
89 safePath(path);
90 final existing = await service.statOrNull(path);
91 if (existing != null) {
92 if (!context.mounted) return null;
93 final ok = await confirm(context, 'overwriteExisting', detail: path);
94 if (!ok) return null;
95 }
96 await service.saveFile(path, bytes, force: true);
97 final source = RemoteSource(
98 service: () => c.sftpFor(profile.id),
99 profileId: profile.id,
100 profileName: profile.name,
101 path: path,
102 );
103 await source.load(maxBytes: maxEditableBytes);
104 return source;
105 } on Failure catch (e) {
106 if (e.code == 'atomicNotPossible' || e.code == 'ownerWouldChange') {
107 if (context.mounted) showFailure(context, e);
108 return null;
109 }
110 rethrow;
111 }
114Future<String?> _askPath(BuildContext context, String initial) async {
115 final field = TextEditingController(text: initial);
116 final result = await showDialog<String>(
117 context: context,
118 builder: (ctx) => AlertDialog(
119 title: Text(ctx.tr('saveToServer')),
120 content: TextField(
121 controller: field,
122 autofocus: true,
123 autocorrect: false,
124 decoration: InputDecoration(labelText: ctx.tr('remotePath')),
125 ),
126 actions: [
127 TextButton(
128 onPressed: () => Navigator.pop(ctx),
129 child: Text(ctx.tr('cancel')),
130 ),
131 FilledButton(
132 onPressed: () => Navigator.pop(ctx, field.text.trim()),
133 child: Text(ctx.tr('save')),
134 ),
135 ],
136 ),
137 );
138 Future.delayed(const Duration(seconds: 1), field.dispose);
139 return result;
SHA-256e45ea8f0da63589f072d69b97dfeafab638fc4da0778499b085013995806be14

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