المصدر المنشور
تنزيل الشيفرة المصدرية بصيغة ZIP
CH-J SM Mobile
تصفّح الأدلة والملفات الخاصة بإصدار محدد من التطبيق.
CH-J / SM Mobile
175 ملفات
sm-mobile-20261016T185235Z-8e93ecc1b59a
تاريخ الإضافة: · تاريخ البناء:
1
import 'dart:async';2
import 'package:flutter/services.dart';3
import '../core/failure.dart';5
/// A file or folder chosen by the user in the system file picker.6
///7
/// [id] is opaque: on iOS a security-scoped bookmark (optionally followed by8
/// a path inside a chosen folder), on Android a `content://` document URI.9
/// Access only exists within what the user granted; nothing here bypasses10
/// the system sandbox.11
class DeviceDocument {12
final String id, name;13
final int? size;14
final bool writable, directory;15
final String? mime;16
final DateTime? modified;18
/// Where it lives, for display only (provider or folder name).19
final String? location;20
const DeviceDocument({21
required this.id,22
required this.name,23
this.size,24
this.writable = false,25
this.directory = false,26
this.mime,27
this.modified,28
this.location,29
});31
factory DeviceDocument.fromMap(Map m) => DeviceDocument(32
id: m['id'] as String,33
name: m['name'] as String? ?? '?',34
size: (m['size'] as num?)?.toInt(),35
writable: m['writable'] == true,36
directory: m['directory'] == true,37
mime: m['mime'] as String?,38
modified: m['modified'] == null39
? null40
: DateTime.fromMillisecondsSinceEpoch((m['modified'] as num).toInt()),41
location: m['location'] as String?,42
);44
Map<String, dynamic> toJson() => {45
'id': id,46
'name': name,47
'size': ?size,48
'writable': writable,49
'directory': directory,50
'mime': ?mime,51
'modified': ?modified?.millisecondsSinceEpoch,52
'location': ?location,53
};54
}56
/// Access to files on the device through the system document picker57
/// (iOS/iPadOS Files, Android Storage Access Framework).58
abstract interface class DocumentPlatform {59
/// Lets the user choose one or more files. Empty when cancelled.60
Future<List<DeviceDocument>> pick({bool multiple = false});62
/// Lets the user choose a folder. Null when cancelled.63
Future<DeviceDocument?> pickFolder();65
/// The entries of a folder chosen with [pickFolder].66
Future<List<DeviceDocument>> list(DeviceDocument folder);68
/// Current name, size, date and write permission.69
Future<DeviceDocument> stat(DeviceDocument doc);71
Future<Uint8List> read(DeviceDocument doc);73
/// Replaces the content of [doc] in place (coordinated with other apps on74
/// iOS). Throws 'readOnly' when the provider does not allow writing.75
Future<DeviceDocument> write(DeviceDocument doc, Uint8List bytes);77
/// Lets the user choose where to save a new file. Null when cancelled.78
Future<DeviceDocument?> saveAs(79
String suggestedName,80
Uint8List bytes, {81
String? mime,82
});84
/// Creates a new file in a chosen folder.85
Future<DeviceDocument> create(86
DeviceDocument folder,87
String name,88
Uint8List bytes, {89
String? mime,90
});92
/// Shows a local file (in the app's temporary directory) with the system93
/// viewer: Quick Look on iOS (in the app), an app chosen by the user on94
/// Android. Returns false when nothing on the device can show it.95
Future<bool> preview(String path, {String? mime});97
/// Android only: renders PDF pages with the system PdfRenderer.98
Future<int> pdfPageCount(String path);99
Future<Uint8List> pdfPage(String path, int index, int width);101
/// Files handed to SM Mobile by other apps ("Open in", share).102
Stream<List<DeviceDocument>> get incoming;104
/// Releases a persisted permission when a recent entry is removed.105
Future<void> forget(DeviceDocument doc);106
}108
class MethodChannelDocuments implements DocumentPlatform {109
static const channel = MethodChannel('eu.chj.sm_mobile/documents');110
final _incoming = StreamController<List<DeviceDocument>>.broadcast();111
bool _listening = false;113
MethodChannelDocuments();115
Future<T?> _call<T>(String method, [Object? args]) async {116
try {117
return await channel.invokeMethod<T>(method, args);118
} on PlatformException catch (e) {119
throw Failure(switch (e.code) {120
'readOnly' => 'readOnlyError',121
'denied' => 'permissionDenied',122
'notFound' => 'fileNotFound',123
'noSpace' => 'noSpace',124
'stale' => 'documentStale',125
'busy' => 'operationFailed',126
_ => 'fileError',127
});128
} on MissingPluginException {129
throw const Failure('unsupportedPlatform');130
}131
}133
List<DeviceDocument> _docs(List? raw) => [134
for (final m in raw ?? const []) DeviceDocument.fromMap(m as Map),135
];137
@override138
Future<List<DeviceDocument>> pick({bool multiple = false}) async =>139
_docs(await _call<List>('pick', {'multiple': multiple}));141
@override142
Future<DeviceDocument?> pickFolder() async {143
final m = await _call<Map>('pickFolder');144
return m == null ? null : DeviceDocument.fromMap(m);145
}147
@override148
Future<List<DeviceDocument>> list(DeviceDocument folder) async =>149
_docs(await _call<List>('list', {'id': folder.id}));151
@override152
Future<DeviceDocument> stat(DeviceDocument doc) async =>153
DeviceDocument.fromMap((await _call<Map>('stat', {'id': doc.id}))!);155
@override156
Future<Uint8List> read(DeviceDocument doc) async =>157
(await _call<Uint8List>('read', {'id': doc.id}))!;159
@override160
Future<DeviceDocument> write(DeviceDocument doc, Uint8List bytes) async =>161
DeviceDocument.fromMap(162
(await _call<Map>('write', {'id': doc.id, 'bytes': bytes}))!,163
);165
@override166
Future<DeviceDocument?> saveAs(167
String suggestedName,168
Uint8List bytes, {169
String? mime,170
}) async {171
final m = await _call<Map>('saveAs', {172
'name': suggestedName,173
'bytes': bytes,174
'mime': mime ?? 'application/octet-stream',175
});176
return m == null ? null : DeviceDocument.fromMap(m);177
}179
@override180
Future<DeviceDocument> create(181
DeviceDocument folder,182
String name,183
Uint8List bytes, {184
String? mime,185
}) async => DeviceDocument.fromMap(186
(await _call<Map>('create', {187
'folder': folder.id,188
'name': name,189
'bytes': bytes,190
'mime': mime ?? 'application/octet-stream',191
}))!,192
);194
@override195
Future<bool> preview(String path, {String? mime}) async =>196
await _call<bool>('preview', {'path': path, 'mime': ?mime}) ?? false;198
@override199
Future<int> pdfPageCount(String path) async =>200
await _call<int>('pdfPageCount', {'path': path}) ?? 0;202
@override203
Future<Uint8List> pdfPage(String path, int index, int width) async =>204
(await _call<Uint8List>('pdfPage', {205
'path': path,206
'index': index,207
'width': width,208
}))!;210
@override211
Stream<List<DeviceDocument>> get incoming {212
if (!_listening) {213
_listening = true;214
channel.setMethodCallHandler((call) async {215
if (call.method == 'incoming') {216
_incoming.add(_docs(call.arguments as List?));217
}218
});219
// Files that arrived before Dart was listening (cold start).220
unawaited(221
_call<List>('takeIncoming')222
.then((raw) {223
final docs = _docs(raw);224
if (docs.isNotEmpty) _incoming.add(docs);225
})226
.catchError((Object _) {}),227
);228
}229
return _incoming.stream;230
}232
@override233
Future<void> forget(DeviceDocument doc) async {234
try {235
await _call<void>('forget', {'id': doc.id});236
} catch (_) {237
/* Nothing to release. */238
}239
}240
}SHA-256
9633a5bf460cef3d55e8298e3575b55b589145676c2adf5aa37c9bb6539d2236CH-J Proprietary Software License 1.14 · تُتاح الشيفرة المصدرية بموجب CH-J Proprietary Software License 1.14. وإتاحتها لا تغيّر شروط الترخيص ولا تمنح حقوقًا إضافية.
SHA-256 للأرشيف
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655