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:typed_data';2
import 'package:flutter/material.dart';3
import '../../core/app_controller.dart';4
import '../../core/failure.dart';5
import '../../l10n/strings.dart';6
import '../../services/editor/languages.dart';7
import '../../services/editor/recents.dart';8
import '../../services/editor/sources.dart';9
import '../../services/sftp_service.dart';10
import '../common.dart';12
/// Remembers [source] in the encrypted list of recent files.13
Future<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
}29
}31
/// Saves [bytes] under a new name: on the device (system save dialog) or on32
/// the connected server. Only ever on explicit user choice. Returns the new33
/// source, or null when cancelled.34
Future<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
}112
}114
Future<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;140
}SHA-256
e45ea8f0da63589f072d69b97dfeafab638fc4da0778499b085013995806be14CH-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