diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c983eb..37ce36c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.0.0 + +* Added support for Files (sendFilePayload) +* Breaking Change (sendPayload method signature is changed) +* Updated Example and Readme for file transfer + ## 0.1.3+1 * Update documentation and Readme diff --git a/README.md b/README.md index 025ddb0..6b8aeaa 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ An **android** flutter plugin for the Nearby Connections API * [Accept Connection](#accept-connection) * [Sending Data](#sending-data) * [Sending Bytes Payload](#sending-bytes-payload) + * [Sending Files](#sending-file-payload) ## Setup @@ -120,7 +121,23 @@ Nearby().acceptConnection( ### Sending Bytes Payload ```dart -Nearby().sendPayload(endpointId, bytes_array); +Nearby().sendBytesPayload(endpointId, bytes_array); + +// payloads are recieved by callback given to acceptConnection method. +``` +### Sending File Payload +You need to send the File Payload and File Name seperately. + +File is stored in DOWNLOAD_DIRECTORY and given a generic name +So you would need to rename the file on receivers end. + +```dart + +//creates file with generic name (without extension) in Downloads Directory +Nearby().sendFilePayload(endpointId, filePath); + +//Send filename as well so that receiver can rename the file +Nearby().sendBytesPayload(endpointId,fileNameEncoded); // payloads are recieved by callback given to acceptConnection method. ``` diff --git a/android/src/main/java/com/pkmnapps/nearby_connections/NearbyConnectionsPlugin.java b/android/src/main/java/com/pkmnapps/nearby_connections/NearbyConnectionsPlugin.java index 7a552d2..d9231ef 100644 --- a/android/src/main/java/com/pkmnapps/nearby_connections/NearbyConnectionsPlugin.java +++ b/android/src/main/java/com/pkmnapps/nearby_connections/NearbyConnectionsPlugin.java @@ -78,12 +78,12 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { result.success(null); break; case "stopAdvertising": - Log.d("NearbyCon java", "stopAdvertising"); + Log.d("nearby_connections", "stopAdvertising"); Nearby.getConnectionsClient(activity).stopAdvertising(); result.success(null); break; case "stopDiscovery": - Log.d("NearbyCon java", "stopDiscovery"); + Log.d("nearby_connections", "stopDiscovery"); Nearby.getConnectionsClient(activity).stopDiscovery(); result.success(null); break; @@ -101,7 +101,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { - Log.d("NearbyCon java", "startAdvertising"); + Log.d("nearby_connections", "startAdvertising"); result.success(true); } }) @@ -125,7 +125,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { - Log.d("NearbyCon java", "startDiscovery"); + Log.d("nearby_connections", "startDiscovery"); result.success(true); } }) @@ -138,12 +138,12 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { break; } case "stopAllEndpoints": - Log.d("NearbyCon java", "stopAllEndpoints"); + Log.d("nearby_connections", "stopAllEndpoints"); Nearby.getConnectionsClient(activity).stopAllEndpoints(); result.success(null); break; case "disconnectFromEndpoint": { - Log.d("NearbyCon java", "disconnectFromEndpoint"); + Log.d("nearby_connections", "disconnectFromEndpoint"); String endpointId = call.argument("endpointId"); assert endpointId != null; Nearby.getConnectionsClient(activity).disconnectFromEndpoint(endpointId); @@ -151,7 +151,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { break; } case "requestConnection": { - Log.d("NearbyCon java", "requestConnection"); + Log.d("nearby_connections", "requestConnection"); String userNickName = (String) call.argument("userNickName"); String endpointId = (String) call.argument("endpointId"); @@ -220,7 +220,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { assert endpointId != null; assert bytes != null; Nearby.getConnectionsClient(activity).sendPayload(endpointId, Payload.fromBytes(bytes)); - Log.d("NearbyCon java", "sentPayload"); + Log.d("nearby_connections", "sentPayload"); result.success(true); break; } @@ -236,10 +236,10 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { Payload filePayload = Payload.fromFile(file); Nearby.getConnectionsClient(activity).sendPayload(endpointId, filePayload); - Log.d("NearbyCon java", "sentFilePayload"); + Log.d("nearby_connections", "sentFilePayload"); result.success(filePayload.getId()); //return payload id to dart } catch (FileNotFoundException e) { - Log.e("NearbyCon java", "File not found", e); + Log.e("nearby_connections", "File not found", e); result.error("Failure", "File Not found", null); return; } @@ -253,7 +253,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { private final ConnectionLifecycleCallback advertConnectionLifecycleCallback = new ConnectionLifecycleCallback() { @Override public void onConnectionInitiated(@NonNull String endpointId, @NonNull ConnectionInfo connectionInfo) { - Log.d("NearbyCon java", "ad.onConnectionInitiated"); + Log.d("nearby_connections", "ad.onConnectionInitiated"); Map args = new HashMap<>(); args.put("endpointId", endpointId); args.put("endpointName", connectionInfo.getEndpointName()); @@ -264,7 +264,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { @Override public void onConnectionResult(@NonNull String endpointId, @NonNull ConnectionResolution connectionResolution) { - Log.d("NearbyCon java", "ad.onConnectionResult"); + Log.d("nearby_connections", "ad.onConnectionResult"); Map args = new HashMap<>(); args.put("endpointId", endpointId); int statusCode = -1; @@ -290,7 +290,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { @Override public void onDisconnected(@NonNull String endpointId) { - Log.d("NearbyCon java", "ad.onDisconnected"); + Log.d("nearby_connections", "ad.onDisconnected"); Map args = new HashMap<>(); args.put("endpointId", endpointId); channel.invokeMethod("ad.onDisconnected", args); @@ -300,7 +300,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { private final ConnectionLifecycleCallback discoverConnectionLifecycleCallback = new ConnectionLifecycleCallback() { @Override public void onConnectionInitiated(@NonNull String endpointId, @NonNull ConnectionInfo connectionInfo) { - Log.d("NearbyCon java", "dis.onConnectionInitiated"); + Log.d("nearby_connections", "dis.onConnectionInitiated"); Map args = new HashMap<>(); args.put("endpointId", endpointId); args.put("endpointName", connectionInfo.getEndpointName()); @@ -311,7 +311,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { @Override public void onConnectionResult(@NonNull String endpointId, @NonNull ConnectionResolution connectionResolution) { - Log.d("NearbyCon java", "dis.onConnectionResult"); + Log.d("nearby_connections", "dis.onConnectionResult"); Map args = new HashMap<>(); args.put("endpointId", endpointId); int statusCode = -1; @@ -337,7 +337,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { @Override public void onDisconnected(@NonNull String endpointId) { - Log.d("NearbyCon java", "dis.onDisconnected"); + Log.d("nearby_connections", "dis.onDisconnected"); Map args = new HashMap<>(); args.put("endpointId", endpointId); channel.invokeMethod("dis.onDisconnected", args); @@ -347,7 +347,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { private final PayloadCallback payloadCallback = new PayloadCallback() { @Override public void onPayloadReceived(@NonNull String endpointId, @NonNull Payload payload) { - Log.d("NearbyCon java", "onPayloadReceived"); + Log.d("nearby_connections", "onPayloadReceived"); Map args = new HashMap<>(); args.put("endpointId", endpointId); args.put("payloadId", payload.getId()); @@ -366,7 +366,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { public void onPayloadTransferUpdate(@NonNull String endpointId, @NonNull PayloadTransferUpdate payloadTransferUpdate) { //required for files and streams - Log.d("NearbyCon java", "onPayloadTransferUpdate"); + Log.d("nearby_connections", "onPayloadTransferUpdate"); Map args = new HashMap<>(); args.put("endpointId", endpointId); args.put("payloadId", payloadTransferUpdate.getPayloadId()); @@ -381,7 +381,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { private final EndpointDiscoveryCallback endpointDiscoveryCallback = new EndpointDiscoveryCallback() { @Override public void onEndpointFound(@NonNull String endpointId, @NonNull DiscoveredEndpointInfo discoveredEndpointInfo) { - Log.d("NearbyCon java", "onEndpointFound"); + Log.d("nearby_connections", "onEndpointFound"); Map args = new HashMap<>(); args.put("endpointId", endpointId); args.put("endpointName", discoveredEndpointInfo.getEndpointName()); @@ -391,7 +391,7 @@ public class NearbyConnectionsPlugin implements MethodCallHandler { @Override public void onEndpointLost(@NonNull String endpointId) { - Log.d("NearbyCon java", "onEndpointLost"); + Log.d("nearby_connections", "onEndpointLost"); Map args = new HashMap<>(); args.put("endpointId", endpointId); channel.invokeMethod("dis.onEndpointLost", args); diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 07be9c6..e9f568f 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -32,7 +32,6 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.pkmnapps.nearby_connections_example" minSdkVersion 16 targetSdkVersion 28 @@ -43,7 +42,6 @@ android { buildTypes { release { - // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } diff --git a/example/lib/main.dart b/example/lib/main.dart index 82251bb..a996030 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -160,7 +160,7 @@ class _MyBodyState extends State { onPressed: () async { String a = Random().nextInt(100).toString(); showSnackbar("Sending $a to $cId"); - Nearby().sendPayload(cId, Uint8List.fromList(a.codeUnits)); + Nearby().sendBytesPayload(cId, Uint8List.fromList(a.codeUnits)); }, ), RaisedButton( diff --git a/example/pubspec.lock b/example/pubspec.lock index fafe0b3..3a0dfaf 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -73,7 +73,7 @@ packages: path: ".." relative: true source: path - version: "0.1.3+1" + version: "1.0.0" path: dependency: transitive description: diff --git a/lib/nearby_connections.dart b/lib/nearby_connections.dart index 8fd22c2..1d48426 100644 --- a/lib/nearby_connections.dart +++ b/lib/nearby_connections.dart @@ -1,427 +1,6 @@ -import 'dart:async'; -import 'dart:typed_data'; +/// https://pub.dev/packages/nearby_connections#-readme-tab- +library nearby_connections; -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -/// **P2P_CLUSTER** - best for small payloads and multiplayer games -/// -/// **P2P_STAR** - best for medium payloads, higher bandwidth than cluster -/// -/// **P2P_POINT_TO_POINT** - single connection, very high bandwidth -enum Strategy { P2P_CLUSTER, P2P_STAR, P2P_POINT_TO_POINT } -enum Status { CONNECTED, REJECTED, ERROR } -enum PayloadStatus { NONE, SUCCESS, FAILURE, IN_PROGRRESS, CANCELED } -enum PayloadType { NONE, BYTES, FILE, STREAM } -typedef void OnConnctionInitiated( - String endpointId, ConnectionInfo connectionInfo); -typedef void OnConnectionResult(String endpointId, Status status); -typedef void OnDisconnected(String endpointId); - -typedef void OnEndpointFound( - String endpointId, String endpointName, String serviceId); -typedef void OnEndpointLost(String endpointId); - -/// Bytes may be null if [Payload.type] is not [PayloadType.BYTES] -class Payload { - int id; - PayloadType type; - Uint8List bytes; - - Payload({ - this.id, - this.bytes, - this.type = PayloadType.NONE, - }); -} - -/// gives payload status, bytes transfered and total bytes. -class PayloadTransferUpdate { - int id, bytesTransferred, totalBytes; - PayloadStatus status; - - PayloadTransferUpdate({ - this.id, - this.bytesTransferred, - this.totalBytes, - this.status = PayloadStatus.NONE, - }); -} - -/// For Bytes, this contains the bytes data -/// -/// For File, this marks the start of transfer -/// -/// Uint8List bytes may be null, if [type] is not [PayloadType.BYTES] -typedef void OnPayloadReceived(String endpointId, Payload payload); - -/// Called only once for Bytes and repeatedly for File until transfer is complete -typedef void OnPayloadTransferUpdate( - String endpointId, PayloadTransferUpdate payloadTransferUpdate); - -// typedef void OnPayloadTransferUpdate(); -/// The NearbyConnection class -/// -/// Only one instance is maintained -/// even on calling Nearby() multiple times -/// -/// All methods are asynchronous. -class Nearby { - //for maintaining only 1 instance of this class - static Nearby _instance; - - factory Nearby() { - if (_instance == null) { - _instance = Nearby._(); - } - return _instance; - } - - Nearby._() { - _channel.setMethodCallHandler((handler) { - print("=========in handler============"); - - Map args = handler.arguments; - - print(handler.method); - args.forEach((s, d) { - print(s + " : " + d.toString()); - }); - print("====================="); - switch (handler.method) { - case "ad.onConnectionInitiated": - String endpointId = args['endpointId']; - String endpointName = args['endpointName']; - String authenticationToken = args['authenticationToken']; - bool isIncomingConnection = args['isIncomingConnection']; - - _advertConnectionInitiated?.call( - endpointId, - ConnectionInfo( - endpointName, authenticationToken, isIncomingConnection)); - - return null; - case "ad.onConnectionResult": - String endpointId = args['endpointId']; - Status statusCode = Status.values[args['statusCode']]; - - _advertConnectionResult?.call(endpointId, statusCode); - - return null; - case "ad.onDisconnected": - String endpointId = args['endpointId']; - - _advertDisconnected?.call(endpointId); - - return null; - - case "dis.onConnectionInitiated": - String endpointId = args['endpointId']; - String endpointName = args['endpointName']; - String authenticationToken = args['authenticationToken']; - bool isIncomingConnection = args['isIncomingConnection']; - - _discoverConnectionInitiated?.call( - endpointId, - ConnectionInfo( - endpointName, authenticationToken, isIncomingConnection)); - - return null; - case "dis.onConnectionResult": - String endpointId = args['endpointId']; - Status statusCode = Status.values[args['statusCode']]; - - _discoverConnectionResult?.call(endpointId, statusCode); - - return null; - case "dis.onDisconnected": - String endpointId = args['endpointId']; - - _discoverDisconnected?.call(endpointId); - - return null; - - case "dis.onEndpointFound": - print("in switch"); - String endpointId = args['endpointId']; - String endpointName = args['endpointName']; - String serviceId = args['serviceId']; - _onEndpointFound?.call(endpointId, endpointName, serviceId); - - return null; - case "dis.onEndpointLost": - String endpointId = args['endpointId']; - - _onEndpointLost?.call(endpointId); - - return null; - case "onPayloadReceived": - String endpointId = args['endpointId']; - int type = args['type']; - Uint8List bytes = args['bytes']; - int payloadId = args['payloadId']; - - Payload payload = Payload( - type: PayloadType.values[type], - bytes: bytes, - id: payloadId, - ); - - _onPayloadReceived?.call(endpointId, payload); - - break; - case "onPayloadTransferUpdate": - String endpointId = args['endpointId']; - int payloadId = args['payloadId']; - int status = args['status']; - int bytesTransferred = args['bytesTransferred']; - int totalBytes = args['totalBytes']; - - PayloadTransferUpdate payloadTransferUpdate = PayloadTransferUpdate( - id: payloadId, - status: PayloadStatus.values[status], - bytesTransferred: bytesTransferred, - totalBytes: totalBytes, - ); - - _onPayloadTransferUpdate?.call(endpointId, payloadTransferUpdate); - break; - } - return null; - }); - } - - OnConnctionInitiated _advertConnectionInitiated, _discoverConnectionInitiated; - OnConnectionResult _advertConnectionResult, _discoverConnectionResult; - OnDisconnected _advertDisconnected, _discoverDisconnected; - - OnEndpointFound _onEndpointFound; - OnEndpointLost _onEndpointLost; - - OnPayloadReceived _onPayloadReceived; - OnPayloadTransferUpdate _onPayloadTransferUpdate; - - static const MethodChannel _channel = - const MethodChannel('nearby_connections'); - - /// Convinience method - /// - /// retruns true/false based on location permissions. - /// Discovery cannot be started with insufficient permission - Future checkPermissions() async => await _channel.invokeMethod( - 'checkPermissions', - ); - - /// Convinience method - /// - /// Asks location permission - Future askPermission() async { - await _channel.invokeMethod( - 'askPermissions', - ); - } - - /// Start Advertising - /// - /// [userNickName] and [strategy] should not be null - Future startAdvertising( - String userNickName, - Strategy strategy, { - @required OnConnctionInitiated onConnectionInitiated, - @required OnConnectionResult onConnectionResult, - @required OnDisconnected onDisconnected, - }) async { - assert(userNickName != null && strategy != null); - - this._advertConnectionInitiated = onConnectionInitiated; - this._advertConnectionResult = onConnectionResult; - this._advertDisconnected = onDisconnected; - - return await _channel.invokeMethod('startAdvertising', { - 'userNickName': userNickName, - 'strategy': strategy.index - }); - } - - /// Stop Advertising - /// - /// This doesn't disconnect from any connected Endpoint - /// - /// For disconnection use - /// [stopAllEndpoints] or [disconnectFromEndpoint] - Future stopAdvertising() async { - await _channel.invokeMethod('stopAdvertising'); - } - - /// Start Discovery - /// - /// [userNickName] and [strategy] should not be null - Future startDiscovery( - String userNickName, - Strategy strategy, { - @required OnEndpointFound onEndpointFound, - @required OnEndpointLost onEndpointLost, - }) async { - assert(userNickName != null && strategy != null); - this._onEndpointFound = onEndpointFound; - this._onEndpointLost = onEndpointLost; - - return await _channel.invokeMethod('startDiscovery', { - 'userNickName': userNickName, - 'strategy': strategy.index - }); - } - - /// Stop Discovery - /// - /// This doesn't disconnect from any connected Endpoint - /// - /// It is reccomended to call this method - /// once you have connected to an endPoint - /// as it uses heavy radio operations - /// which may affect connection speed and integrity - Future stopDiscovery() async { - await _channel.invokeMethod('stopDiscovery'); - } - - /// Stop All Endpoints - /// - /// Disconnects all connections, - /// this will call the onDisconnected method on callbacks of - /// all connected endPoints - Future stopAllEndpoints() async { - await _channel.invokeMethod('stopAllEndpoints'); - } - - /// Disconnect from Endpoints - /// - /// Disconnects the connections to given endPointId - /// this will call the onDisconnected method on callbacks of - /// connected endPoint - Future disconnectFromEndpoint(String endpointId) async { - await _channel.invokeMethod( - 'disconnectFromEndpoint', {'endpointId': endpointId}); - } - - /// Request Connection - /// - /// Call this method when Discoverer calls the - /// [OnEndpointFound] method - /// - /// This will call the [OnConnctionInitiated] method on - /// both the endPoint and this - Future requestConnection( - String userNickName, - String endpointId, { - @required OnConnctionInitiated onConnectionInitiated, - @required OnConnectionResult onConnectionResult, - @required OnDisconnected onDisconnected, - }) async { - this._discoverConnectionInitiated = onConnectionInitiated; - this._discoverConnectionResult = onConnectionResult; - this._discoverDisconnected = onDisconnected; - - return await _channel.invokeMethod( - 'requestConnection', - { - 'userNickName': userNickName, - 'endpointId': endpointId, - }, - ); - } - - /// Needs be called by both discoverer and advertiser - /// to connect - /// - /// Call this in [OnConnctionInitiated] - /// to accept an incoming connection - /// - /// [OnConnectionResult] is called on both - /// only if both of them accept the connection - Future acceptConnection( - String endpointId, { - @required OnPayloadReceived onPayLoadRecieved, - OnPayloadTransferUpdate onPayloadTransferUpdate, - }) async { - this._onPayloadReceived = onPayLoadRecieved; - this._onPayloadTransferUpdate = onPayloadTransferUpdate; - return await _channel.invokeMethod( - 'acceptConnection', - { - 'endpointId': endpointId, - }, - ); - } - - /// Reject Connection - /// - /// To be called by both discoverer and advertiser - /// - /// Call this in [OnConnctionInitiated] - /// to reject an incoming connection - /// - /// [OnConnectionResult] is called on both - /// even if one of them rejects the connection - Future rejectConnection(String endpointId) async { - return await _channel.invokeMethod( - 'rejectConnection', - { - 'endpointId': endpointId, - }, - ); - } - - /// Send bytes [Uint8List] payload to endpoint - /// - /// Convert String to Uint8List as follows - /// - /// ```dart - /// String a = "hello"; - /// Uint8List bytes = Uint8List.fromList(a.codeUnits); - /// ``` - Future sendPayload(String endpointId, Uint8List bytes) async { - return await _channel.invokeMethod( - 'sendPayload', - { - 'endpointId': endpointId, - 'bytes': bytes, - }, - ); - } - - /// Returns the payloadID as soon as file transfer has begun - /// - /// File is received in DOWNLOADS_DIRECTORY and is given a generic name - /// without extension - /// You must also send a bytes payload to send the filename and extension - /// so that receiver can rename the file accordingly - /// Send the payloadID and filename to receiver as bytes payload - Future sendFilePayload(String endpointId, String filePath) async { - return await _channel.invokeMethod( - 'sendFilePayload', - { - 'endpointId': endpointId, - 'filePath': filePath, - }, - ); - } -} - -/// ConnectionInfo class -/// -/// Its a parameter in [OnConnctionInitiated] -/// -/// [endPointName] is userNickName of requester -/// -/// [authenticationToken] can be used to check the connection security -/// it must be same on both devices -class ConnectionInfo { - String endpointName, authenticationToken; - bool isIncomingConnection; - - ConnectionInfo( - this.endpointName, this.authenticationToken, this.isIncomingConnection); -} -//TODO remove errors on failure for smooth experience -//TODO expose only relevant parts as library -//TODO publish to pub.dartlang +export 'src/classes.dart'; +export 'src/defs.dart'; +export 'src/nearby_connections.dart'; \ No newline at end of file diff --git a/lib/src/classes.dart b/lib/src/classes.dart new file mode 100644 index 0000000..4633f09 --- /dev/null +++ b/lib/src/classes.dart @@ -0,0 +1,45 @@ +// contains custom classes +import 'dart:typed_data'; + +import 'package:nearby_connections/src/defs.dart'; + +/// Bytes may be null if [Payload.type] is not [PayloadType.BYTES] +class Payload { + int id; + PayloadType type; + Uint8List bytes; + + Payload({ + this.id, + this.bytes, + this.type = PayloadType.NONE, + }); +} + +class PayloadTransferUpdate { + int id, bytesTransferred, totalBytes; + PayloadStatus status; + + PayloadTransferUpdate({ + this.id, + this.bytesTransferred, + this.totalBytes, + this.status = PayloadStatus.NONE, + }); +} + +/// ConnectionInfo class +/// +/// Its a parameter in [OnConnctionInitiated] +/// +/// [endPointName] is userNickName of requester +/// +/// [authenticationToken] can be used to check the connection security +/// it must be same on both devices +class ConnectionInfo { + String endpointName, authenticationToken; + bool isIncomingConnection; + + ConnectionInfo( + this.endpointName, this.authenticationToken, this.isIncomingConnection); +} \ No newline at end of file diff --git a/lib/src/defs.dart b/lib/src/defs.dart new file mode 100644 index 0000000..7b3e063 --- /dev/null +++ b/lib/src/defs.dart @@ -0,0 +1,46 @@ +// contains enums and typedefs + +import 'package:nearby_connections/src/classes.dart'; + +/// **P2P_CLUSTER** - best for small payloads and multiplayer games +/// +/// **P2P_STAR** - best for medium payloads, higher bandwidth than cluster +/// +/// **P2P_POINT_TO_POINT** - single connection, very high bandwidth +enum Strategy { P2P_CLUSTER, P2P_STAR, P2P_POINT_TO_POINT } +enum Status { CONNECTED, REJECTED, ERROR } +enum PayloadStatus { NONE, SUCCESS, FAILURE, IN_PROGRRESS, CANCELED } +enum PayloadType { NONE, BYTES, FILE, STREAM } + +// +// +// +// Advertising lifecycle callbacks +// +typedef void OnConnctionInitiated( + String endpointId, ConnectionInfo connectionInfo); +typedef void OnConnectionResult(String endpointId, Status status); +typedef void OnDisconnected(String endpointId); + +// +// +// +// Discovery lifecycle callbacks +// +typedef void OnEndpointFound( + String endpointId, String endpointName, String serviceId); +typedef void OnEndpointLost(String endpointId); + +// +// +// +/// For Bytes, this contains the bytes data +/// +/// For File, this marks the start of transfer +/// +/// Uint8List bytes may be null, if [type] is not [PayloadType.BYTES] +typedef void OnPayloadReceived(String endpointId, Payload payload); + +/// Called only once for Bytes and repeatedly for File until transfer is complete +typedef void OnPayloadTransferUpdate( + String endpointId, PayloadTransferUpdate payloadTransferUpdate); diff --git a/lib/src/nearby_connections.dart b/lib/src/nearby_connections.dart new file mode 100644 index 0000000..86f4960 --- /dev/null +++ b/lib/src/nearby_connections.dart @@ -0,0 +1,345 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:nearby_connections/src/defs.dart'; +import 'package:nearby_connections/src/classes.dart'; + +/// The NearbyConnection class +/// +/// Only one instance is maintained +/// even on calling Nearby() multiple times +/// +/// All methods are asynchronous. +class Nearby { + //Singleton pattern for maintaining only 1 instance of this class + static Nearby _instance; + factory Nearby() { + if (_instance == null) { + _instance = Nearby._(); + } + return _instance; + } + + Nearby._() { + _channel.setMethodCallHandler((handler) { + Map args = handler.arguments; + switch (handler.method) { + case "ad.onConnectionInitiated": + String endpointId = args['endpointId']; + String endpointName = args['endpointName']; + String authenticationToken = args['authenticationToken']; + bool isIncomingConnection = args['isIncomingConnection']; + + _advertConnectionInitiated?.call( + endpointId, + ConnectionInfo( + endpointName, authenticationToken, isIncomingConnection)); + + return null; + case "ad.onConnectionResult": + String endpointId = args['endpointId']; + Status statusCode = Status.values[args['statusCode']]; + + _advertConnectionResult?.call(endpointId, statusCode); + + return null; + case "ad.onDisconnected": + String endpointId = args['endpointId']; + + _advertDisconnected?.call(endpointId); + + return null; + + case "dis.onConnectionInitiated": + String endpointId = args['endpointId']; + String endpointName = args['endpointName']; + String authenticationToken = args['authenticationToken']; + bool isIncomingConnection = args['isIncomingConnection']; + + _discoverConnectionInitiated?.call( + endpointId, + ConnectionInfo( + endpointName, authenticationToken, isIncomingConnection)); + + return null; + case "dis.onConnectionResult": + String endpointId = args['endpointId']; + Status statusCode = Status.values[args['statusCode']]; + + _discoverConnectionResult?.call(endpointId, statusCode); + + return null; + case "dis.onDisconnected": + String endpointId = args['endpointId']; + + _discoverDisconnected?.call(endpointId); + + return null; + + case "dis.onEndpointFound": + String endpointId = args['endpointId']; + String endpointName = args['endpointName']; + String serviceId = args['serviceId']; + _onEndpointFound?.call(endpointId, endpointName, serviceId); + + return null; + case "dis.onEndpointLost": + String endpointId = args['endpointId']; + + _onEndpointLost?.call(endpointId); + + return null; + case "onPayloadReceived": + String endpointId = args['endpointId']; + int type = args['type']; + Uint8List bytes = args['bytes']; + int payloadId = args['payloadId']; + + Payload payload = Payload( + type: PayloadType.values[type], + bytes: bytes, + id: payloadId, + ); + + _onPayloadReceived?.call(endpointId, payload); + + break; + case "onPayloadTransferUpdate": + String endpointId = args['endpointId']; + int payloadId = args['payloadId']; + int status = args['status']; + int bytesTransferred = args['bytesTransferred']; + int totalBytes = args['totalBytes']; + + PayloadTransferUpdate payloadTransferUpdate = PayloadTransferUpdate( + id: payloadId, + status: PayloadStatus.values[status], + bytesTransferred: bytesTransferred, + totalBytes: totalBytes, + ); + + _onPayloadTransferUpdate?.call(endpointId, payloadTransferUpdate); + break; + } + return null; + }); + } + + //for advertisers + OnConnctionInitiated _advertConnectionInitiated, _discoverConnectionInitiated; + OnConnectionResult _advertConnectionResult, _discoverConnectionResult; + OnDisconnected _advertDisconnected, _discoverDisconnected; + + //for discoverers + OnEndpointFound _onEndpointFound; + OnEndpointLost _onEndpointLost; + + //for receiving payload + OnPayloadReceived _onPayloadReceived; + OnPayloadTransferUpdate _onPayloadTransferUpdate; + + static const MethodChannel _channel = + const MethodChannel('nearby_connections'); + + /// Convinience method + /// + /// retruns true/false based on location permissions. + /// Discovery cannot be started with insufficient permission + Future checkPermissions() async => await _channel.invokeMethod( + 'checkPermissions', + ); + + /// Convinience method + /// + /// Asks location permission + Future askPermission() async => await _channel.invokeMethod( + 'askPermissions', + ); + + /// Start Advertising + /// + /// [userNickName] and [strategy] should not be null + Future startAdvertising( + String userNickName, + Strategy strategy, { + @required OnConnctionInitiated onConnectionInitiated, + @required OnConnectionResult onConnectionResult, + @required OnDisconnected onDisconnected, + }) async { + assert(userNickName != null && strategy != null); + + this._advertConnectionInitiated = onConnectionInitiated; + this._advertConnectionResult = onConnectionResult; + this._advertDisconnected = onDisconnected; + + return await _channel.invokeMethod('startAdvertising', { + 'userNickName': userNickName, + 'strategy': strategy.index + }); + } + + /// Stop Advertising + /// + /// This doesn't disconnect from any connected Endpoint + /// + /// For disconnection use + /// [stopAllEndpoints] or [disconnectFromEndpoint] + Future stopAdvertising() async { + await _channel.invokeMethod('stopAdvertising'); + } + + /// Start Discovery + /// + /// [userNickName] and [strategy] should not be null + Future startDiscovery( + String userNickName, + Strategy strategy, { + @required OnEndpointFound onEndpointFound, + @required OnEndpointLost onEndpointLost, + }) async { + assert(userNickName != null && strategy != null); + this._onEndpointFound = onEndpointFound; + this._onEndpointLost = onEndpointLost; + + return await _channel.invokeMethod('startDiscovery', { + 'userNickName': userNickName, + 'strategy': strategy.index + }); + } + + /// Stop Discovery + /// + /// This doesn't disconnect from already connected Endpoint + /// + /// It is reccomended to call this method + /// once you have connected to an endPoint + /// as discovery uses heavy radio operations + /// which may affect connection speed and integrity + Future stopDiscovery() async { + await _channel.invokeMethod('stopDiscovery'); + } + + /// Stop All Endpoints + /// + /// Disconnects all connections, + /// this will call the onDisconnected method on callbacks of + /// all connected endPoints + Future stopAllEndpoints() async { + await _channel.invokeMethod('stopAllEndpoints'); + } + + /// Disconnect from Endpoints + /// + /// Disconnects the connections to given endPointId + /// this will call the onDisconnected method on callbacks of + /// connected endPoint + Future disconnectFromEndpoint(String endpointId) async { + await _channel.invokeMethod( + 'disconnectFromEndpoint', {'endpointId': endpointId}); + } + + /// Request Connection + /// + /// Call this method when Discoverer calls the + /// [OnEndpointFound] method + /// + /// This will call the [OnConnctionInitiated] method on + /// both the endPoint and this + Future requestConnection( + String userNickName, + String endpointId, { + @required OnConnctionInitiated onConnectionInitiated, + @required OnConnectionResult onConnectionResult, + @required OnDisconnected onDisconnected, + }) async { + this._discoverConnectionInitiated = onConnectionInitiated; + this._discoverConnectionResult = onConnectionResult; + this._discoverDisconnected = onDisconnected; + + return await _channel.invokeMethod( + 'requestConnection', + { + 'userNickName': userNickName, + 'endpointId': endpointId, + }, + ); + } + + /// Needs be called by both discoverer and advertiser + /// to connect + /// + /// Call this in [OnConnctionInitiated] + /// to accept an incoming connection + /// + /// [OnConnectionResult] is called on both + /// only if both of them accept the connection + Future acceptConnection( + String endpointId, { + @required OnPayloadReceived onPayLoadRecieved, + OnPayloadTransferUpdate onPayloadTransferUpdate, + }) async { + this._onPayloadReceived = onPayLoadRecieved; + this._onPayloadTransferUpdate = onPayloadTransferUpdate; + return await _channel.invokeMethod( + 'acceptConnection', + { + 'endpointId': endpointId, + }, + ); + } + + /// Reject Connection + /// + /// To be called by both discoverer and advertiser + /// + /// Call this in [OnConnctionInitiated] + /// to reject an incoming connection + /// + /// [OnConnectionResult] is called on both + /// even if one of them rejects the connection + Future rejectConnection(String endpointId) async { + return await _channel.invokeMethod( + 'rejectConnection', + { + 'endpointId': endpointId, + }, + ); + } + + /// Send bytes [Uint8List] payload to endpoint + /// + /// Convert String to Uint8List as follows + /// + /// ```dart + /// String a = "hello"; + /// Uint8List bytes = Uint8List.fromList(a.codeUnits); + /// ``` + Future sendBytesPayload(String endpointId, Uint8List bytes) async { + return await _channel.invokeMethod( + 'sendPayload', + { + 'endpointId': endpointId, + 'bytes': bytes, + }, + ); + } + + /// Returns the payloadID as soon as file transfer has begun + /// + /// File is received in DOWNLOADS_DIRECTORY and is given a generic name + /// without extension + /// You must also send a bytes payload to send the filename and extension + /// so that receiver can rename the file accordingly + /// Send the payloadID and filename to receiver as bytes payload + Future sendFilePayload(String endpointId, String filePath) async { + return await _channel.invokeMethod( + 'sendFilePayload', + { + 'endpointId': endpointId, + 'filePath': filePath, + }, + ); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 282b4d8..5d3faff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: nearby_connections -description: Plugin for the android NearbyConnections API. Currently sending bytes (Uint8List) is possible. -version: 0.1.3+1 +description: Plugin for the android NearbyConnections API. Bytes and Files Supported. +version: 1.0.0 author: Prerak Mann homepage: https://github.com/mannprerak2/nearby_connections