feat: transfer with file name
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
- [ ] Translations
|
||||
- [ ] Drop file
|
||||
- [ ] Keep file name
|
||||
- [x] Keep file name
|
||||
- [ ] Use streams
|
||||
- [ ] Better UI/UX
|
||||
- [ ] Remove snackbars
|
||||
- [x] Use https://pub.dev/packages/webcrypto or https://pub.dev/packages/cryptography for efficient web crypto operations
|
||||
- [ ] 2 options: everyone can delete with the link | only the creator can delete
|
||||
- [ ] ox, os, original sha256, original size
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:ndk/ndk.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class FileShareController extends GetxController {
|
||||
final _isLoading = false.obs;
|
||||
@@ -13,7 +14,6 @@ class FileShareController extends GetxController {
|
||||
final _error = RxnString();
|
||||
final _decryptedData = Rxn<Uint8List>();
|
||||
final _metadata = Rxn<FileMetadata>();
|
||||
final _filename = RxnString();
|
||||
final _hasStarted = false.obs;
|
||||
|
||||
bool get isLoading => _isLoading.value;
|
||||
@@ -21,7 +21,6 @@ class FileShareController extends GetxController {
|
||||
String? get error => _error.value;
|
||||
Uint8List? get decryptedData => _decryptedData.value;
|
||||
FileMetadata? get metadata => _metadata.value;
|
||||
String? get filename => _filename.value;
|
||||
bool get hasStarted => _hasStarted.value;
|
||||
|
||||
@override
|
||||
@@ -120,10 +119,15 @@ class FileShareController extends GetxController {
|
||||
|
||||
try {
|
||||
final mimeType = _metadata.value?.fileType ?? 'application/octet-stream';
|
||||
final filename = _metadata.value?.filename ?? 'downloaded_file';
|
||||
|
||||
final nameWithoutExt = p.withoutExtension(filename);
|
||||
final ext = p.extension(filename).replaceFirst('.', '');
|
||||
|
||||
await FileSaver.instance.saveFile(
|
||||
name: _filename.value ?? 'downloaded_file',
|
||||
name: nameWithoutExt,
|
||||
bytes: _decryptedData.value!,
|
||||
fileExtension: mimeType.split('/').last,
|
||||
fileExtension: ext.isEmpty ? mimeType.split('/').last : ext,
|
||||
mimeType: MimeType.other,
|
||||
customMimeType: mimeType,
|
||||
);
|
||||
|
||||
@@ -46,6 +46,7 @@ class HomePageController extends GetxController {
|
||||
final sharedFile = await shareFile(
|
||||
bytes: file.bytes!,
|
||||
contentType: mimeType,
|
||||
filename: file.name,
|
||||
);
|
||||
|
||||
_sharedFile.value = sharedFile;
|
||||
|
||||
@@ -7,6 +7,7 @@ Future<Nip01Event> createEvent({
|
||||
required String recipientPubkey,
|
||||
required String key,
|
||||
required String nonce,
|
||||
String? filename,
|
||||
}) async {
|
||||
final keyPair = Bip340.generatePrivateKey();
|
||||
final signer = Bip340EventSigner(
|
||||
@@ -19,6 +20,7 @@ Future<Nip01Event> createEvent({
|
||||
kind: 15,
|
||||
tags: [
|
||||
if (descriptor.type != null) ["file-type", descriptor.type!],
|
||||
if (filename != null && filename.isNotEmpty) ["filename", filename],
|
||||
["encryption-algorithm", "aes-gcm"],
|
||||
["decryption-key", key],
|
||||
["decryption-nonce", nonce],
|
||||
|
||||
@@ -24,9 +24,7 @@ Future<Uint8List> decryptBlob({
|
||||
0,
|
||||
encryptedBytes.length - macLength,
|
||||
);
|
||||
final macBytes = encryptedBytes.sublist(
|
||||
encryptedBytes.length - macLength,
|
||||
);
|
||||
final macBytes = encryptedBytes.sublist(encryptedBytes.length - macLength);
|
||||
|
||||
// Create secret key from bytes
|
||||
final secretKey = SecretKey(keyBytes);
|
||||
|
||||
@@ -17,9 +17,10 @@ Future<EncryptedBlob> encryptBlob(Uint8List bytes) async {
|
||||
|
||||
// Concatenate ciphertext + MAC for storage
|
||||
final macBytes = secretBox.mac.bytes;
|
||||
final encryptedData = Uint8List.fromList(
|
||||
[...secretBox.cipherText, ...macBytes],
|
||||
);
|
||||
final encryptedData = Uint8List.fromList([
|
||||
...secretBox.cipherText,
|
||||
...macBytes,
|
||||
]);
|
||||
|
||||
return EncryptedBlob(
|
||||
bytes: encryptedData,
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:ndk_flutter/ndk_flutter.dart';
|
||||
Future<SharedFile> shareFile({
|
||||
required Uint8List bytes,
|
||||
String? contentType,
|
||||
String? filename,
|
||||
}) async {
|
||||
final ndk = Ndk(
|
||||
NdkConfig(
|
||||
@@ -41,6 +42,7 @@ Future<SharedFile> shareFile({
|
||||
recipientPubkey: recipientKeyPair.publicKey,
|
||||
key: encryptedBlob.key,
|
||||
nonce: encryptedBlob.nonce,
|
||||
filename: filename,
|
||||
);
|
||||
|
||||
await ndk.broadcast
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:ndk/ndk.dart';
|
||||
|
||||
class FileMetadata {
|
||||
final String? fileType;
|
||||
final String? filename;
|
||||
final String key;
|
||||
final String nonce;
|
||||
final String x;
|
||||
@@ -10,6 +11,7 @@ class FileMetadata {
|
||||
|
||||
FileMetadata({
|
||||
required this.fileType,
|
||||
required this.filename,
|
||||
required this.key,
|
||||
required this.nonce,
|
||||
required this.x,
|
||||
@@ -20,6 +22,7 @@ class FileMetadata {
|
||||
factory FileMetadata.fromEvent(Nip01Event event) {
|
||||
return FileMetadata(
|
||||
fileType: event.getFirstTag('file-type'),
|
||||
filename: event.getFirstTag('filename'),
|
||||
key: event.getFirstTag('decryption-key') ?? '',
|
||||
nonce: event.getFirstTag('decryption-nonce') ?? '',
|
||||
x: event.getFirstTag('x') ?? '',
|
||||
|
||||
+1
-1
@@ -470,7 +470,7 @@ packages:
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
|
||||
@@ -18,6 +18,7 @@ dependencies:
|
||||
ndk: ^0.7.1-dev.19
|
||||
ndk_flutter: ^0.0.2-dev.13
|
||||
nip49: ^1.1.2
|
||||
path: ^1.9.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user