2019-05-10 06:54:05 +00:00
|
|
|
import 'dart:async';
|
2019-05-11 19:13:34 +00:00
|
|
|
import 'dart:typed_data';
|
2019-05-10 06:54:05 +00:00
|
|
|
|
2019-05-11 09:46:11 +00:00
|
|
|
import 'package:flutter/foundation.dart';
|
2019-05-10 06:54:05 +00:00
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
2019-08-06 15:23:37 +00:00
|
|
|
/// **P2P_CLUSTER** - best for small payloads and multiplayer games
|
2019-08-08 08:57:10 +00:00
|
|
|
///
|
2019-08-06 15:23:37 +00:00
|
|
|
/// **P2P_STAR** - best for medium payloads, higher bandwidth than cluster
|
2019-08-08 08:57:10 +00:00
|
|
|
///
|
2019-08-06 15:23:37 +00:00
|
|
|
/// **P2P_POINT_TO_POINT** - single connection, very high bandwidth
|
2019-05-11 10:31:33 +00:00
|
|
|
enum Strategy { P2P_CLUSTER, P2P_STAR, P2P_POINT_TO_POINT }
|
2019-05-11 08:34:12 +00:00
|
|
|
enum Status { CONNECTED, REJECTED, ERROR }
|
2019-08-08 08:57:10 +00:00
|
|
|
enum PayloadStatus { NONE, SUCCESS, FAILURE, IN_PROGRRESS, CANCELED }
|
|
|
|
enum PayloadType { NONE, BYTES, FILES, STREAM }
|
2019-05-11 09:46:11 +00:00
|
|
|
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);
|
|
|
|
|
2019-08-08 08:57:10 +00:00
|
|
|
/// For Bytes, this contains the bytes dala
|
|
|
|
///
|
|
|
|
/// For File, this marks the start of transfer
|
|
|
|
///
|
|
|
|
/// Uint8List bytes may be null, if [payloadType] is not [PayloadType.BYTES]
|
|
|
|
typedef void OnPayloadReceived(
|
|
|
|
String endpointId, Uint8List bytes, PayloadType payloadType);
|
|
|
|
|
|
|
|
/// Called only once for Bytes and repeatedly for File until transfer is complete
|
|
|
|
typedef void OnPayloadTransferUpdate(
|
|
|
|
{String endpointId,
|
|
|
|
int payloadId,
|
|
|
|
PayloadStatus payloadStatus,
|
|
|
|
int bytesTransferred,
|
|
|
|
int totalBytes});
|
|
|
|
|
2019-08-06 15:23:37 +00:00
|
|
|
// typedef void OnPayloadTransferUpdate();
|
2019-05-15 08:13:10 +00:00
|
|
|
/// The NearbyConnection class
|
|
|
|
///
|
|
|
|
/// Only one instance is maintained
|
|
|
|
/// even on calling Nearby() multiple times
|
|
|
|
///
|
|
|
|
/// All methods are asynchronous.
|
2019-05-10 12:54:38 +00:00
|
|
|
class Nearby {
|
|
|
|
//for maintaining only 1 instance of this class
|
2019-05-11 08:34:12 +00:00
|
|
|
static Nearby _instance;
|
|
|
|
|
|
|
|
factory Nearby() {
|
|
|
|
if (_instance == null) {
|
|
|
|
_instance = Nearby._();
|
|
|
|
}
|
|
|
|
return _instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
Nearby._() {
|
|
|
|
_channel.setMethodCallHandler((handler) {
|
2019-05-11 19:13:34 +00:00
|
|
|
print("=========in handler============");
|
|
|
|
|
2019-05-11 18:12:35 +00:00
|
|
|
Map<dynamic, dynamic> args = handler.arguments;
|
|
|
|
|
2019-05-11 08:34:12 +00:00
|
|
|
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'];
|
2019-05-11 09:46:11 +00:00
|
|
|
|
|
|
|
_advertConnectionInitiated?.call(
|
|
|
|
endpointId,
|
|
|
|
ConnectionInfo(
|
|
|
|
endpointName, authenticationToken, isIncomingConnection));
|
|
|
|
|
2019-05-11 08:34:12 +00:00
|
|
|
return null;
|
|
|
|
case "ad.onConnectionResult":
|
|
|
|
String endpointId = args['endpointId'];
|
|
|
|
Status statusCode = Status.values[args['statusCode']];
|
|
|
|
|
2019-05-11 09:46:11 +00:00
|
|
|
_advertConnectionResult?.call(endpointId, statusCode);
|
|
|
|
|
2019-05-11 08:34:12 +00:00
|
|
|
return null;
|
|
|
|
case "ad.onDisconnected":
|
|
|
|
String endpointId = args['endpointId'];
|
|
|
|
|
2019-05-11 09:46:11 +00:00
|
|
|
_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);
|
|
|
|
|
2019-05-11 08:34:12 +00:00
|
|
|
return null;
|
|
|
|
|
|
|
|
case "dis.onEndpointFound":
|
2019-05-11 18:12:35 +00:00
|
|
|
print("in switch");
|
2019-05-11 08:34:12 +00:00
|
|
|
String endpointId = args['endpointId'];
|
|
|
|
String endpointName = args['endpointName'];
|
|
|
|
String serviceId = args['serviceId'];
|
2019-05-11 09:46:11 +00:00
|
|
|
_onEndpointFound?.call(endpointId, endpointName, serviceId);
|
|
|
|
|
2019-05-11 08:34:12 +00:00
|
|
|
return null;
|
|
|
|
case "dis.onEndpointLost":
|
|
|
|
String endpointId = args['endpointId'];
|
|
|
|
|
2019-05-11 09:46:11 +00:00
|
|
|
_onEndpointLost?.call(endpointId);
|
|
|
|
|
2019-05-11 08:34:12 +00:00
|
|
|
return null;
|
2019-05-11 19:13:34 +00:00
|
|
|
case "onPayloadReceived":
|
|
|
|
String endpointId = args['endpointId'];
|
2019-08-08 08:57:10 +00:00
|
|
|
int type = args['type'];
|
2019-05-11 19:13:34 +00:00
|
|
|
Uint8List bytes = args['bytes'];
|
|
|
|
|
2019-08-08 08:57:10 +00:00
|
|
|
_onPayloadReceived?.call(endpointId, bytes, PayloadType.values[type]);
|
2019-05-11 19:13:34 +00:00
|
|
|
|
2019-08-08 08:57:10 +00:00
|
|
|
break;
|
|
|
|
case "onPayloadTransferUpdate":
|
|
|
|
String endpointId = args['endpointId'];
|
|
|
|
int payloadId = args['payloadId'];
|
|
|
|
int success = args['success'];
|
|
|
|
int bytesTransferred = args['bytesTransferred'];
|
|
|
|
int totalBytes = args['totalBytes'];
|
|
|
|
|
|
|
|
_onPayloadTransferUpdate?.call(
|
|
|
|
endpointId: endpointId,
|
|
|
|
payloadId: payloadId,
|
|
|
|
payloadStatus: PayloadStatus.values[success],
|
|
|
|
bytesTransferred: bytesTransferred,
|
|
|
|
totalBytes: totalBytes,
|
|
|
|
);
|
2019-05-11 19:13:34 +00:00
|
|
|
break;
|
2019-05-11 08:34:12 +00:00
|
|
|
}
|
2019-07-15 13:51:22 +00:00
|
|
|
return null;
|
2019-05-11 08:34:12 +00:00
|
|
|
});
|
|
|
|
}
|
2019-05-10 12:54:38 +00:00
|
|
|
|
2019-05-11 09:46:11 +00:00
|
|
|
OnConnctionInitiated _advertConnectionInitiated, _discoverConnectionInitiated;
|
|
|
|
OnConnectionResult _advertConnectionResult, _discoverConnectionResult;
|
|
|
|
OnDisconnected _advertDisconnected, _discoverDisconnected;
|
|
|
|
|
|
|
|
OnEndpointFound _onEndpointFound;
|
|
|
|
OnEndpointLost _onEndpointLost;
|
|
|
|
|
2019-05-11 19:13:34 +00:00
|
|
|
OnPayloadReceived _onPayloadReceived;
|
2019-08-08 08:57:10 +00:00
|
|
|
OnPayloadTransferUpdate _onPayloadTransferUpdate;
|
2019-05-11 19:13:34 +00:00
|
|
|
|
2019-05-10 06:54:05 +00:00
|
|
|
static const MethodChannel _channel =
|
|
|
|
const MethodChannel('nearby_connections');
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Convinience method
|
|
|
|
///
|
|
|
|
/// retruns true/false based on location permissions.
|
|
|
|
/// Discovery cannot be started with insufficient permission
|
2019-05-10 12:54:38 +00:00
|
|
|
Future<bool> checkPermissions() async => await _channel.invokeMethod(
|
|
|
|
'checkPermissions',
|
|
|
|
);
|
2019-05-10 21:18:45 +00:00
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Convinience method
|
|
|
|
///
|
|
|
|
/// Asks location permission
|
2019-05-10 21:18:45 +00:00
|
|
|
Future<void> askPermission() async {
|
|
|
|
await _channel.invokeMethod(
|
|
|
|
'askPermissions',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Start Advertising
|
|
|
|
///
|
|
|
|
/// [userNickName] and [strategy] should not be null
|
2019-05-11 09:46:11 +00:00
|
|
|
Future<bool> startAdvertising(
|
|
|
|
String userNickName,
|
2019-05-11 10:31:33 +00:00
|
|
|
Strategy strategy, {
|
2019-05-11 09:46:11 +00:00
|
|
|
@required OnConnctionInitiated onConnectionInitiated,
|
|
|
|
@required OnConnectionResult onConnectionResult,
|
|
|
|
@required OnDisconnected onDisconnected,
|
|
|
|
}) async {
|
2019-05-10 21:18:45 +00:00
|
|
|
assert(userNickName != null && strategy != null);
|
|
|
|
|
2019-05-11 09:46:11 +00:00
|
|
|
this._advertConnectionInitiated = onConnectionInitiated;
|
|
|
|
this._advertConnectionResult = onConnectionResult;
|
|
|
|
this._advertDisconnected = onDisconnected;
|
|
|
|
|
2019-05-10 21:18:45 +00:00
|
|
|
return await _channel.invokeMethod('startAdvertising', <String, dynamic>{
|
|
|
|
'userNickName': userNickName,
|
|
|
|
'strategy': strategy.index
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Stop Advertising
|
|
|
|
///
|
|
|
|
/// This doesn't disconnect from any connected Endpoint
|
|
|
|
///
|
|
|
|
/// For disconnection use
|
|
|
|
/// [stopAllEndpoints] or [disconnectFromEndpoint]
|
2019-05-10 21:18:45 +00:00
|
|
|
Future<void> stopAdvertising() async {
|
|
|
|
await _channel.invokeMethod('stopAdvertising');
|
|
|
|
}
|
2019-05-11 08:34:12 +00:00
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Start Discovery
|
|
|
|
///
|
|
|
|
/// [userNickName] and [strategy] should not be null
|
2019-05-11 08:34:12 +00:00
|
|
|
Future<bool> startDiscovery(
|
2019-05-11 09:46:11 +00:00
|
|
|
String userNickName,
|
2019-05-11 10:31:33 +00:00
|
|
|
Strategy strategy, {
|
|
|
|
@required OnEndpointFound onEndpointFound,
|
|
|
|
@required OnEndpointLost onEndpointLost,
|
2019-05-11 09:46:11 +00:00
|
|
|
}) async {
|
2019-05-11 08:34:12 +00:00
|
|
|
assert(userNickName != null && strategy != null);
|
2019-05-11 18:12:35 +00:00
|
|
|
this._onEndpointFound = onEndpointFound;
|
|
|
|
this._onEndpointLost = onEndpointLost;
|
2019-05-11 08:34:12 +00:00
|
|
|
|
|
|
|
return await _channel.invokeMethod('startDiscovery', <String, dynamic>{
|
|
|
|
'userNickName': userNickName,
|
|
|
|
'strategy': strategy.index
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// 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
|
2019-05-11 08:34:12 +00:00
|
|
|
Future<void> stopDiscovery() async {
|
|
|
|
await _channel.invokeMethod('stopDiscovery');
|
|
|
|
}
|
2019-05-11 09:46:11 +00:00
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Stop All Endpoints
|
|
|
|
///
|
|
|
|
/// Disconnects all connections,
|
|
|
|
/// this will call the onDisconnected method on callbacks of
|
|
|
|
/// all connected endPoints
|
2019-05-11 09:46:11 +00:00
|
|
|
Future<void> stopAllEndpoints() async {
|
|
|
|
await _channel.invokeMethod('stopAllEndpoints');
|
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Disconnect from Endpoints
|
|
|
|
///
|
|
|
|
/// Disconnects the connections to given endPointId
|
|
|
|
/// this will call the onDisconnected method on callbacks of
|
|
|
|
/// connected endPoint
|
2019-05-11 09:46:11 +00:00
|
|
|
Future<void> disconnectFromEndpoint(String endpointId) async {
|
|
|
|
await _channel.invokeMethod(
|
|
|
|
'disconnectFromEndpoint', <String, dynamic>{'endpointId': endpointId});
|
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Request Connection
|
|
|
|
///
|
|
|
|
/// Call this method when Discoverer calls the
|
|
|
|
/// [OnEndpointFound] method
|
|
|
|
///
|
|
|
|
/// This will call the [OnConnctionInitiated] method on
|
|
|
|
/// both the endPoint and this
|
2019-05-11 09:46:11 +00:00
|
|
|
Future<bool> 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',
|
|
|
|
<String, dynamic>{
|
|
|
|
'userNickName': userNickName,
|
|
|
|
'endpointId': endpointId,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Accept Connection
|
|
|
|
///
|
|
|
|
/// Needs be called by both discoverer and advertiser
|
|
|
|
/// to connect
|
|
|
|
///
|
|
|
|
/// Call this in [OnConnctionInitiated]
|
|
|
|
/// to accept an incoming connection
|
2019-07-15 13:51:22 +00:00
|
|
|
///
|
|
|
|
/// [OnConnectionResult] is called on both
|
2019-05-15 08:13:10 +00:00
|
|
|
/// only if both of them accept the connection
|
2019-05-11 19:13:34 +00:00
|
|
|
Future<bool> acceptConnection(
|
|
|
|
String endpointId, {
|
|
|
|
@required OnPayloadReceived onPayLoadRecieved,
|
2019-08-08 08:57:10 +00:00
|
|
|
OnPayloadTransferUpdate onPayloadTransferUpdate,
|
2019-05-11 19:13:34 +00:00
|
|
|
}) async {
|
|
|
|
this._onPayloadReceived = onPayLoadRecieved;
|
2019-08-08 08:57:10 +00:00
|
|
|
this._onPayloadTransferUpdate = onPayloadTransferUpdate;
|
2019-05-11 09:46:11 +00:00
|
|
|
return await _channel.invokeMethod(
|
|
|
|
'acceptConnection',
|
|
|
|
<String, dynamic>{
|
|
|
|
'endpointId': endpointId,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Reject Connection
|
|
|
|
///
|
|
|
|
/// To be called by both discoverer and advertiser
|
|
|
|
///
|
|
|
|
/// Call this in [OnConnctionInitiated]
|
|
|
|
/// to reject an incoming connection
|
2019-07-15 13:51:22 +00:00
|
|
|
///
|
|
|
|
/// [OnConnectionResult] is called on both
|
|
|
|
/// even if one of them rejects the connection
|
2019-05-11 09:46:11 +00:00
|
|
|
Future<bool> rejectConnection(String endpointId) async {
|
|
|
|
return await _channel.invokeMethod(
|
2019-05-11 20:00:07 +00:00
|
|
|
'rejectConnection',
|
2019-05-11 09:46:11 +00:00
|
|
|
<String, dynamic>{
|
|
|
|
'endpointId': endpointId,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2019-05-11 19:13:34 +00:00
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Send bytes [Uint8List] payload to endpoint
|
2019-07-15 13:51:22 +00:00
|
|
|
///
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Convert String to Uint8List as follows
|
2019-07-15 13:51:22 +00:00
|
|
|
///
|
2019-05-15 08:13:10 +00:00
|
|
|
/// ```dart
|
|
|
|
/// String a = "hello";
|
|
|
|
/// Uint8List bytes = Uint8List.fromList(a.codeUnits);
|
|
|
|
/// ```
|
2019-05-11 19:13:34 +00:00
|
|
|
Future<void> sendPayload(String endpointId, Uint8List bytes) async {
|
|
|
|
return await _channel.invokeMethod(
|
|
|
|
'sendPayload',
|
|
|
|
<String, dynamic>{
|
|
|
|
'endpointId': endpointId,
|
|
|
|
'bytes': bytes,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2019-08-08 08:57:10 +00:00
|
|
|
|
|
|
|
/// 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<int> sendFilePayload(String endpointId, String filePath) async {
|
|
|
|
return await _channel.invokeMethod(
|
|
|
|
'sendFilePayload',
|
|
|
|
<String, dynamic>{
|
|
|
|
'endpointId': endpointId,
|
|
|
|
'filePath': filePath,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2019-05-10 21:18:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-15 08:13:10 +00:00
|
|
|
/// ConnectionInfo class
|
2019-07-15 13:51:22 +00:00
|
|
|
///
|
2019-05-15 08:13:10 +00:00
|
|
|
/// Its a parameter in [OnConnctionInitiated]
|
2019-07-15 13:51:22 +00:00
|
|
|
///
|
2019-05-15 08:13:10 +00:00
|
|
|
/// [endPointName] is userNickName of requester
|
2019-07-15 13:51:22 +00:00
|
|
|
///
|
2019-08-06 15:23:37 +00:00
|
|
|
/// [authenticationToken] can be used to check the connection security
|
2019-05-15 08:13:10 +00:00
|
|
|
/// it must be same on both devices
|
2019-05-10 21:18:45 +00:00
|
|
|
class ConnectionInfo {
|
2019-05-11 09:46:11 +00:00
|
|
|
String endpointName, authenticationToken;
|
2019-05-10 21:18:45 +00:00
|
|
|
bool isIncomingConnection;
|
|
|
|
|
|
|
|
ConnectionInfo(
|
2019-05-11 09:46:11 +00:00
|
|
|
this.endpointName, this.authenticationToken, this.isIncomingConnection);
|
2019-05-10 06:54:05 +00:00
|
|
|
}
|
2019-05-11 20:00:07 +00:00
|
|
|
//TODO remove errors on failure for smooth experience
|
|
|
|
//TODO expose only relevant parts as library
|
|
|
|
//TODO publish to pub.dartlang
|