feat(*): add sender to files pack result

This commit is contained in:
ksenia312
2024-02-04 22:25:57 +01:00
parent a1ac4488ca
commit d6840a4313
25 changed files with 199 additions and 152 deletions
+19 -14
View File
@@ -270,6 +270,7 @@ class AppService extends ChangeNotifier {
final result = await _nearbyService.android?.checkWifiService();
if (result ?? false) {
updateState(AppState.readyToDiscover);
startListeningConnectionInfo();
return true;
}
return false;
@@ -289,7 +290,6 @@ class AppService extends ChangeNotifier {
final result = await _nearbyService.discover();
if (result) {
updateState(AppState.discoveringPeers);
startListeningConnectionInfo();
}
} catch (e) {
if (kDebugMode) {
@@ -333,11 +333,6 @@ class AppService extends ChangeNotifier {
updateState(AppState.discoveringPeers);
}
Future<void> stopListeningConnectionInfo() async {
await connectionInfoSubscription?.cancel();
connectionInfoSubscription = null;
}
Future<void> connect(NearbyDeviceBase device) async {
try {
await _nearbyService.connect(device);
@@ -366,6 +361,11 @@ class AppService extends ChangeNotifier {
notifyListeners();
}
Future<void> stopListeningConnectionInfo() async {
await connectionInfoSubscription?.cancel();
connectionInfoSubscription = null;
}
Future<void> startListeningConnectedDevice(NearbyDeviceBase device) async {
updateState(AppState.loadingConnection);
try {
@@ -404,7 +404,7 @@ class AppService extends ChangeNotifier {
Future<void> startCommunicationChannel({
ValueChanged<ReceivedNearbyMessage>? listener,
ValueChanged<List<NearbyFileInfo>>? onFilesSaved,
ValueChanged<ReceivedNearbyFilesPack>? onFilesSaved,
}) async {
final messagesListener = NearbyServiceMessagesListener(
onCreated: () {
@@ -424,16 +424,18 @@ class AppService extends ChangeNotifier {
? Directory('storage/emulated/0/Download')
: await getApplicationDocumentsDirectory();
for (final nearbyFile in event) {
final newFile = await nearbyFile.file.copy(
'${directory.path}/${DateTime.now().microsecondsSinceEpoch}.${nearbyFile.info.extension}',
for (final nearbyFile in event.files) {
final newFile = await File(nearbyFile.path).copy(
'${directory.path}/${DateTime.now().microsecondsSinceEpoch}.${nearbyFile.extension}',
);
if (!await newFile.exists()) {
await newFile.create();
}
files.add(NearbyFileInfo(path: newFile.path));
}
onFilesSaved?.call(files);
onFilesSaved?.call(
ReceivedNearbyFilesPack(sender: event.sender, files: files),
);
},
);
@@ -492,7 +494,7 @@ class AppService extends ChangeNotifier {
);
}
Future<void> disconnect(NearbyDeviceBase device) async {
Future<void> disconnect([NearbyDeviceBase? device]) async {
try {
await _nearbyService.disconnect(device);
} catch (e) {
@@ -883,10 +885,13 @@ class _ConnectedBody extends StatelessWidget {
);
}
void _onFileSaved(BuildContext context, List<NearbyFileInfo> files) {
void _onFileSaved(BuildContext context, ReceivedNearbyFilesPack pack) {
final senderSubtitle = 'From ${pack.sender.displayName} '
'(ID: ${pack.sender.id})';
AppShackBar.show(
Scaffold.of(context).context,
'${files.length} files saved! \n${files.map((e) => e.name).join('\n')}',
'${pack.files.length} files saved! \n${pack.files.map((e) => e.name).join('\n')}',
subtitle: senderSubtitle,
);
}
}
+7 -8
View File
@@ -6,30 +6,29 @@
//
import Foundation
import MultipeerConnectivity
class NearbyStartCommand {
init( id: String, filesCount: Int) {
self.id = id
init(senderName: String, filesCount: Int) {
self.senderName = senderName
self.filesCount = filesCount
}
static func fromUserInfo(userInfo: NearbyUserInfo)-> NearbyStartCommand? {
if let id = userInfo.dictionary["id"] as? String ,
if let name = userInfo.dictionary["name"] as? String,
let filesCount = userInfo.dictionary["filesCount"] as? Int
{
return NearbyStartCommand(
id: id, filesCount: filesCount
)
return NearbyStartCommand(senderName: name, filesCount: filesCount)
}
return nil
}
func toDictionary() -> [String: Any] {
return ["id": id, "filesCount": filesCount]
return ["name": senderName, "filesCount": filesCount]
}
let id: String
let senderName: String
let filesCount: Int
}
+4 -2
View File
@@ -47,15 +47,17 @@ extension NearbySession: MCSessionDelegate {
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
guard let localURL = localURL else { return }
let destinationURL = localURL.deletingLastPathComponent().appendingPathComponent("\(resourceName)")
var destinationURL = localURL.deletingLastPathComponent().appendingPathComponent("\(resourceName)")
if FileManager.default.fileExists(atPath: destinationURL.path) {
destinationURL = localURL.deletingLastPathComponent().appendingPathComponent("New_\(resourceName)")
}
do {
try FileManager.default.moveItem(at: localURL, to: destinationURL)
} catch {
Logger.error(message: "Error moving file: \(error)")
}
NotificationCenter.default.post(
name: ON_RESOURCE_RECEIVED,
object: nil,
+7 -2
View File
@@ -124,8 +124,13 @@ class NearbyManager: NSObject {
do {
let device = NearbyDevicesStore.instance.find(for: receiverId)
if let requireDevice = device {
try requireDevice.session?.session?.send(
try JSONSerialization.data(withJSONObject: NearbyStartCommand( id: id, filesCount: paths.count).toDictionary()),
let command = NearbyStartCommand(
senderName: requireDevice.name,
filesCount: paths.count
).toDictionary()
try requireDevice.session?.session?.send(
try JSONSerialization.data(withJSONObject: command),
toPeers: [requireDevice.peerID],
with: MCSessionSendDataMode.reliable
)
+5 -1
View File
@@ -33,13 +33,17 @@ extension NearbyServicePlugin {
}
@objc func onResourceReceived(notification: Notification) {
DispatchQueue.main.async {
if let userInfo = NearbyUserInfo.fromDictionary(userInfo: notification.userInfo) {
if let url = userInfo.dictionary["url"] as? URL {
NearbyFilesStore.instance.add(url: url)
if (NearbyFilesStore.instance.checkIsFull()) {
self.channel.invokeMethod(DART_COMMAND_RESOURCES_RECEIVED, arguments: NearbyFilesStore.instance.toDartFormat())
self.channel.invokeMethod(DART_COMMAND_RESOURCES_RECEIVED, arguments: NearbyFilesStore.instance.toDartFormat(peerID: userInfo.peerID))
NearbyFilesStore.instance.clear()
}
}
}
+25 -11
View File
@@ -6,18 +6,19 @@
//
import Foundation
import MultipeerConnectivity
class NearbyFilesStore {
static let instance = NearbyFilesStore()
private var paths : [String] = []
private var id: String? = nil
private var senderName: String? = nil
private var maxCount: Int = 0
private var count: Int = 0
func startReceiving(command: NearbyStartCommand) {
self.paths.removeAll()
self.id = command.id
self.senderName = command.senderName
self.maxCount = command.filesCount
self.count = 0
}
@@ -31,16 +32,29 @@ class NearbyFilesStore {
return maxCount <= count
}
func toDartFormat() -> String? {
let pathsObject = paths.map { ["path": $0]}
do {
let jsonData = try JSONSerialization.data(withJSONObject: pathsObject)
if let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
func clear() {
self.paths.removeAll()
self.senderName = nil
self.maxCount = 0
self.count = 0
}
func toDartFormat(peerID: MCPeerID) -> String? {
if (senderName != nil) {
let object = [
"files": paths.map { ["path": $0]},
"sender": ["id": peerID.displayName, "displayName": senderName!]
] as [String : Any]
do {
let jsonData = try JSONSerialization.data(withJSONObject: object)
if let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
}
} catch {
return nil
}
} catch {
return "[]"
return nil
}
return "[]"
return nil
}
}
+2 -1
View File
@@ -195,7 +195,8 @@ abstract class NearbyService {
/// Note that if [Platform.isIOS] == true, [NearbyIOSDevice] should be passed.
/// If [Platform.isAndroid] == true, [NearbyAndroidDevice] should be passed.
///
Future<bool> disconnect(NearbyDeviceBase device);
/// For IOS [device] is required!!!
Future<bool> disconnect([NearbyDeviceBase? device]);
///
/// If the device is already connected, it does not mean that you can
+1 -1
View File
@@ -1,3 +1,3 @@
export 'nearby_device_base.dart';
export 'nearby_message_base.dart';
export 'nearby_message_content_base.dart';
export 'nearby_message_content_base.dart';
+1 -1
View File
@@ -1,3 +1,3 @@
export 'nearby_device_mapper.dart';
export 'nearby_received_interface.dart';
export 'nearby_outgoing_interface.dart';
export 'nearby_outgoing_interface.dart';
@@ -32,5 +32,3 @@ abstract interface class NearbyDeviceMapper {
///
NearbyDeviceBase? mapToDevice(dynamic value);
}
+2 -1
View File
@@ -3,5 +3,6 @@ export 'nearby_device_status.dart';
export 'nearby_message.dart';
export 'nearby_message_content.dart';
export 'communication_channel_state.dart';
export 'nearby_file.dart';
export 'nearby_file_info.dart';
export 'nearby_message_content_type.dart';
export 'nearby_files_pack.dart';
@@ -1,4 +1,3 @@
import 'dart:io';
import 'package:nearby_service/nearby_service.dart';
///
@@ -8,46 +7,7 @@ import 'package:nearby_service/nearby_service.dart';
/// From the communication channel, you usually get
/// the [NearbyMessageFilesRequest] request first.
/// After that, you can send positive [NearbyMessageFilesResponse] and
/// get the list of [NearbyFile].
///
final class NearbyFile {
///
/// Pass [info] assigned to file to be sent.
///
const NearbyFile({
required this.info,
required this.file,
});
///
/// Quick info about the file
///
final NearbyFileInfo info;
///
/// A file that you can save in your phone if needed
///
final File file;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NearbyFile &&
runtimeType == other.runtimeType &&
info == other.info &&
file == other.file;
@override
int get hashCode => info.hashCode ^ file.hashCode;
@override
String toString() {
return 'NearbyFile{info: $info, file: $file}';
}
}
///
/// Quick info about the file
/// get the list of [NearbyFileInfo].
///
class NearbyFileInfo {
///
+54
View File
@@ -0,0 +1,54 @@
import 'package:nearby_service/nearby_service.dart';
///
/// Used to provide result [files] that was got from [sender].
///
/// Can be received from [NearbyServiceFilesListener] only.
///
class ReceivedNearbyFilesPack implements NearbyReceivedInterface {
const ReceivedNearbyFilesPack({
required this.sender,
required this.files,
});
factory ReceivedNearbyFilesPack.fromJson(Map<String, dynamic>? json) {
return ReceivedNearbyFilesPack(
sender: NearbyDeviceInfo.fromJson(json?['sender']),
files: [
...?(json?['files'] as List?)?.map(
(e) => NearbyFileInfo.fromJson(e as Map<String, dynamic>),
),
],
);
}
@override
final NearbyDeviceInfo sender;
final List<NearbyFileInfo> files;
Map<String, dynamic> toJson() {
return {
'sender': sender.toJson(),
'files': [
...files.map((e) => e.toJson()),
],
};
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ReceivedNearbyFilesPack &&
runtimeType == other.runtimeType &&
sender == other.sender &&
files == other.files;
@override
int get hashCode => sender.hashCode ^ files.hashCode;
@override
String toString() {
return 'NearbyFilesPack{sender: $sender, files: $files}';
}
}
@@ -42,7 +42,7 @@ class NearbyConnectionAndroidInfo {
final bool isGroupOwner;
///
/// Indicates if the current device is the group owner.
/// Indicates if a p2p group has been successfully formed.
/// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo)
///
final bool groupFormed;
@@ -18,15 +18,15 @@ final class NearbyAndroidDevice extends NearbyDeviceBase {
///
NearbyAndroidDevice({
required String deviceName,
required super.status,
required this.deviceAddress,
required this.isGroupOwner,
required this.isServiceDiscoveryCapable,
required this.primaryDeviceType,
required this.wpsKeypadSupported,
required this.wpsPbcSupported,
required this.wpsDisplaySupported,
this.isGroupOwner = false,
this.isServiceDiscoveryCapable = false,
this.primaryDeviceType = kNearbyUnknown,
this.wpsKeypadSupported = false,
this.wpsPbcSupported = false,
this.wpsDisplaySupported = false,
this.secondaryDeviceType,
super.status = NearbyDeviceStatus.unavailable,
}) : super(
info: NearbyDeviceInfo(
displayName: deviceName,
@@ -65,9 +65,8 @@ class NearbyAndroidService extends NearbyService {
/// Note! Requires [NearbyAndroidDevice] to be passed.
///
@override
Future<bool> disconnect(NearbyDeviceBase device) {
_requireAndroidDevice(device);
return NearbyServiceAndroidPlatform.instance.disconnect(device.info.id);
Future<bool> disconnect([NearbyDeviceBase? device]) {
return NearbyServiceAndroidPlatform.instance.disconnect();
}
///
@@ -51,7 +51,7 @@ abstract class NearbyServiceAndroidPlatform extends PlatformInterface {
throw UnimplementedError('connect() has not been implemented.');
}
Future<bool> disconnect(String deviceAddress) {
Future<bool> disconnect() {
throw UnimplementedError('disconnect() has not been implemented.');
}
@@ -57,12 +57,8 @@ class MethodChannelAndroidNearbyService extends NearbyServiceAndroidPlatform {
}
@override
Future<bool> disconnect(String deviceAddress) async {
return (await methodChannel.invokeMethod<bool?>(
"disconnect",
{"deviceAddress": deviceAddress},
)) ??
false;
Future<bool> disconnect() async {
return (await methodChannel.invokeMethod<bool?>("disconnect")) ?? false;
}
@override
@@ -11,6 +11,7 @@ class FileSocketsManager {
final _serverWaitingRequests = <String, HttpRequest>{};
NearbyServiceFilesListener? _filesListener;
NearbyDeviceInfo? _sender;
void setListener(NearbyServiceFilesListener? listener) {
_filesListener = listener;
@@ -26,22 +27,30 @@ class FileSocketsManager {
Future<void> handleFileMessageContent(
NearbyMessageFilesContent content, {
required NearbyDeviceInfo? sender,
required NearbyAndroidCommunicationChannelData androidData,
required bool isReceived,
}) async {
if (sender != null) {
_sender = sender;
Logger.debug('Sender was set to $_sender');
}
final shouldStartSocket = content.byType(
onFilesResponse: (response) => response.response,
onFilesRequest: (_) => true,
) ??
false;
if (shouldStartSocket) {
final alreadyExists = _filesSockets[content.id] != null;
if (shouldStartSocket && !alreadyExists) {
final info = await _service.getConnectionInfo();
if (info != null && info.groupFormed) {
if (info.isGroupOwner) {
await _startFilesServer(content);
if (content is NearbyMessageFilesResponse && isReceived) {
await _tryTransferData(content);
if (isReceived && content is NearbyMessageFilesResponse) {
await _startDataTransfer(content);
}
} else {
await _connectToFilesSocket(
@@ -49,13 +58,11 @@ class FileSocketsManager {
connectionData: androidData,
ownerIpAddress: info.ownerIpAddress,
);
if (content is NearbyMessageFilesRequest && !isReceived) {
await _tryTransferData(content);
if (!isReceived && content is NearbyMessageFilesRequest) {
await _startDataTransfer(content);
}
}
}
} else {
_filesSockets.remove(content.id);
}
}
@@ -107,9 +114,7 @@ class FileSocketsManager {
}
}
Future<void> _startFilesServer(
NearbyMessageFilesContent content,
) async {
Future<void> _startFilesServer(NearbyMessageFilesContent content) async {
final request = _serverWaitingRequests[content.id];
if (request != null) {
@@ -130,8 +135,9 @@ class FileSocketsManager {
required Future<WebSocket?> Function() onCreateSocket,
}) async {
final socket = await onCreateSocket();
if (socket != null) {
if (socket != null && _sender != null) {
_filesSockets[content.id] = FilesSocket.startListening(
sender: _sender!,
content: content,
socket: socket,
listener: _filesListener,
@@ -143,7 +149,7 @@ class FileSocketsManager {
return false;
}
Future<void> _tryTransferData(NearbyMessageFilesContent content) async {
Future<void> _startDataTransfer(NearbyMessageFilesContent content) async {
final filesSocket = _filesSockets[content.id];
if (filesSocket != null) {
Logger.debug('Start transferring the files pack ${content.id}');
@@ -96,13 +96,7 @@ class NearbySocketService {
},
),
);
if (message.content is NearbyMessageFilesContent) {
_fileSocketsManager.handleFileMessageContent(
message.content as NearbyMessageFilesContent,
androidData: _androidData,
isReceived: false,
);
}
_handleMessage(message);
}
return true;
}
@@ -211,13 +205,7 @@ class NearbySocketService {
.listen(
(message) async {
try {
if (message.content is NearbyMessageFilesContent) {
_fileSocketsManager.handleFileMessageContent(
message.content as NearbyMessageFilesContent,
androidData: _androidData,
isReceived: true,
);
}
_handleMessage(message);
socketListener.onData(message);
} catch (e) {
Logger.error(e);
@@ -244,4 +232,15 @@ class NearbySocketService {
state.value = CommunicationChannelState.notConnected;
}
}
void _handleMessage(NearbyMessageBase message) {
if (message.content is NearbyMessageFilesContent) {
_fileSocketsManager.handleFileMessageContent(
message.content as NearbyMessageFilesContent,
isReceived: message is ReceivedNearbyMessage,
sender: message is ReceivedNearbyMessage ? message.sender : null,
androidData: _androidData,
);
}
}
}
@@ -140,7 +140,8 @@ class NearbyIOSService extends NearbyService {
/// Note! Requires [NearbyIOSDevice] to be passed.
///
@override
Future<bool> disconnect(NearbyDeviceBase device) async {
Future<bool> disconnect([NearbyDeviceBase? device]) async {
if (device == null) return false;
_requireIOSDevice(device);
final result = await NearbyServiceIOSPlatform.instance.disconnect(
device.info.id,
@@ -185,9 +186,9 @@ class NearbyIOSService extends NearbyService {
cancelOnError: eventListener.cancelOnError,
);
_resourcesSubscription = NearbyServiceIOSPlatform.instance.resourcesStream
.map(ResourcesStreamMapper.toFiles)
.map(ResourcesStreamMapper.toFilesPack)
.where((event) => event != null)
.cast<List<NearbyFile>>()
.cast<ReceivedNearbyFilesPack>()
.listen(
(e) => filesListener?.onData.call(e),
onDone: filesListener?.onDone,
@@ -46,7 +46,7 @@ class NearbyServiceMessagesListener
/// Stream Subscription Listener.
///
class NearbyServiceFilesListener
extends NearbyServiceSocketListener<List<NearbyFile>> {
extends NearbyServiceSocketListener<ReceivedNearbyFilesPack> {
///
/// It is required to pass the [onData] parameter to process the
/// data that came through the stream.
+2 -1
View File
@@ -31,7 +31,8 @@ class NearbyServiceException implements Exception {
);
}
factory NearbyServiceException.invalidMessage(NearbyMessageContentBase content) {
factory NearbyServiceException.invalidMessage(
NearbyMessageContentBase content) {
return NearbyServiceException(
'The message="$content" is not valid',
);
+13 -6
View File
@@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart';
class FilesSocket {
FilesSocket.startListening({
required this.sender,
required this.content,
required this.listener,
required this.onDestroy,
@@ -31,9 +32,10 @@ class FilesSocket {
final NearbyMessageFilesContent content;
final void Function(String) onDestroy;
final NearbyServiceFilesListener? listener;
final NearbyDeviceInfo sender;
final WebSocket _socket;
final _files = <NearbyFile>[];
final _files = <NearbyFileInfo>[];
final _bytesTable = <String, List<int>>{'0': []};
final _futures = <Future>[];
@@ -65,7 +67,13 @@ class FilesSocket {
} else if (event == finishCommand) {
await Future.wait(_futures);
Logger.info('Files pack ${content.id} was created');
listener?.onData.call(_files);
listener?.onData.call(
ReceivedNearbyFilesPack(
sender: sender,
files: _files,
),
);
onDestroy(content.id);
}
}
@@ -76,13 +84,12 @@ class FilesSocket {
final fileInfo = content.files[index];
final directory = await getTemporaryDirectory();
final file = File('${directory.path}/${fileInfo.name}');
await file.writeAsBytes(bytes);
final updatedFileInfo = NearbyFileInfo(path: file.path);
final nearbyFile = NearbyFile(file: file, info: fileInfo);
_files.add(nearbyFile);
_files.add(updatedFileInfo);
Logger.info('File ${nearbyFile.info.name} was created');
Logger.info('File ${updatedFileInfo.name} was created');
} catch (e) {
Logger.error(e);
}
+7 -12
View File
@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/json_decoder.dart';
@@ -20,6 +18,8 @@ abstract class MessagesStreamMapper {
static ReceivedNearbyMessage? toMessage(dynamic event) {
try {
final decoded = JSONDecoder.decodeMap(event);
if (decoded == null) return null;
return ReceivedNearbyMessage.fromJson(decoded);
} catch (e) {
throw NearbyServiceException(
@@ -30,17 +30,12 @@ abstract class MessagesStreamMapper {
}
abstract class ResourcesStreamMapper {
static List<NearbyFile>? toFiles(dynamic event) {
static ReceivedNearbyFilesPack? toFilesPack(dynamic event) {
try {
final decoded = JSONDecoder.decodeList(event);
final infoList = [
...?decoded?.map(
(e) => NearbyFileInfo.fromJson(e as Map<String, dynamic>),
)
];
return [
...infoList.map((e) => NearbyFile(info: e, file: File(e.path))),
];
final decoded = JSONDecoder.decodeMap(event);
if (decoded == null) return null;
return ReceivedNearbyFilesPack.fromJson(decoded);
} catch (e) {
throw NearbyServiceException(
'Can\'t convert $event to ReceivedNearbyMessage',