doc(lib): add documentation for the dart side

This commit is contained in:
ksenia312
2024-01-31 22:21:52 +01:00
parent e7995d1c3a
commit 880cded058
28 changed files with 905 additions and 97 deletions
+1 -1
View File
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>11.0</string> <string>12.0</string>
</dict> </dict>
</plist> </plist>
+17 -12
View File
@@ -51,15 +51,18 @@ class MyApp extends StatelessWidget {
'Platform: ${service.platformVersion}\n' 'Platform: ${service.platformVersion}\n'
'Model: ${service.platformModel}', 'Model: ${service.platformModel}',
), ),
if (service.currentDevice != null) if (service.currentDeviceInfo != null)
Text( Text(
'Device Name: ${service.currentDevice!.displayName}\n' 'Device Name: ${service.currentDeviceInfo!.displayName}\n'
'${Platform.isIOS ? 'Device ID: ${service.currentDevice!.id}' : ''}\n', '${Platform.isIOS ? 'Device ID: ${service.currentDeviceInfo!.id}' : ''}',
), ),
if (Platform.isIOS) if (Platform.isIOS)
Text( Text(
'You are ${service.isIOSBrowser ? 'going to find your friend' : 'waiting for another user to connect'}\n', 'You are ${service.isIOSBrowser ? 'going to find your friend' : 'waiting for another user to connect'}',
), ),
Text(
'Communication channel state: ${service.communicationChannelState.name.toUpperCase()}',
)
], ],
), ),
), ),
@@ -170,12 +173,13 @@ enum AppState {
} }
class AppService extends ChangeNotifier { class AppService extends ChangeNotifier {
final _nearbyService = NearbyService.getInstance(); late final _nearbyService = NearbyService.getInstance()
..communicationChannelState.addListener(notifyListeners);
AppState state = AppState.idle; AppState state = AppState.idle;
List<NearbyDevice>? peers; List<NearbyDevice>? peers;
NearbyDevice? connectedDevice; NearbyDevice? connectedDevice;
NearbyDeviceInfo? currentDevice; NearbyDeviceInfo? currentDeviceInfo;
String platformVersion = 'Unknown'; String platformVersion = 'Unknown';
String platformModel = 'Unknown'; String platformModel = 'Unknown';
@@ -183,8 +187,8 @@ class AppService extends ChangeNotifier {
StreamSubscription? peersSubscription; StreamSubscription? peersSubscription;
StreamSubscription? connectedDeviceSubscription; StreamSubscription? connectedDeviceSubscription;
bool get isCommunicationChannelConnecting { CommunicationChannelState get communicationChannelState {
return _nearbyService.isCommunicationChannelConnecting.value; return _nearbyService.communicationChannelState.value;
} }
bool get isIOSBrowser { bool get isIOSBrowser {
@@ -202,7 +206,7 @@ class AppService extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<String> getSavedDeviceName() async { Future<String> getSavedIOSDeviceName() async {
return (await _nearbyService.ios?.getSavedDeviceName()) ?? platformModel; return (await _nearbyService.ios?.getSavedDeviceName()) ?? platformModel;
} }
@@ -211,7 +215,7 @@ class AppService extends ChangeNotifier {
await _nearbyService.initialize( await _nearbyService.initialize(
data: NearbyInitializeData(iosDeviceName: iosDeviceName), data: NearbyInitializeData(iosDeviceName: iosDeviceName),
); );
currentDevice = (await _nearbyService.getCurrentDevice())?.info; currentDeviceInfo = await _nearbyService.getCurrentDeviceInfo();
updateState( updateState(
Platform.isAndroid ? AppState.permissions : AppState.selectClientType, Platform.isAndroid ? AppState.permissions : AppState.selectClientType,
); );
@@ -432,7 +436,7 @@ class _IdleBodyState extends State<_IdleBody> {
@override @override
void initState() { void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
context.read<AppService>().getSavedDeviceName().then((value) { context.read<AppService>().getSavedIOSDeviceName().then((value) {
controller.text = value; controller.text = value;
controller.selection = TextSelection.collapsed(offset: value.length); controller.selection = TextSelection.collapsed(offset: value.length);
setState(() { setState(() {
@@ -719,7 +723,8 @@ class _ConnectedBody extends StatelessWidget {
else else
_DevicePreview(device: device, largeView: true), _DevicePreview(device: device, largeView: true),
const SizedBox(height: 10), const SizedBox(height: 10),
if (!(service.isCommunicationChannelConnecting)) if (service.communicationChannelState !=
CommunicationChannelState.loading)
_ActionButton( _ActionButton(
onTap: () => service.startCommunicationChannel( onTap: () => service.startCommunicationChannel(
listener: (event) => AppShackBar.show( listener: (event) => AppShackBar.show(
+1
View File
@@ -4,6 +4,7 @@ import MultipeerConnectivity
let SERVICE_TYPE = "mp-connection" let SERVICE_TYPE = "mp-connection"
let PEER_ID = "PEER-ID" let PEER_ID = "PEER-ID"
let DEVICE_NAME = "DEVICE-NAME" let DEVICE_NAME = "DEVICE-NAME"
let ON_MESSAGE_RECEIVED = Notification.Name("NearbySessionOnMessageReceived")
class MyDeviceDataGenerator { class MyDeviceDataGenerator {
static func generate(name: String?) -> NearbyDevice { static func generate(name: String?) -> NearbyDevice {
+8 -8
View File
@@ -38,8 +38,8 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
instance, instance,
selector: #selector(messageReceived), selector: #selector(onMessageReceived),
name: NearbySession.messageReceived, name: ON_MESSAGE_RECEIVED,
object: nil object: nil
) )
@@ -49,12 +49,6 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
NearbyDevicesStore.instance.clear() NearbyDevicesStore.instance.clear()
} }
@objc func messageReceived(notification: Notification) {
DispatchQueue.main.async {
let result = NearbyMessageConverter.convert(userInfo: notification.userInfo)
self.channel.invokeMethod("invoke_nearby_service_message_received", arguments: result)
}
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@@ -124,6 +118,12 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
} }
} }
@objc func onMessageReceived(notification: Notification) {
DispatchQueue.main.async {
let result = NearbyMessageConverter.convert(userInfo: notification.userInfo)
self.channel.invokeMethod("invoke_nearby_service_message_received", arguments: result)
}
}
private func getArgument<T>(for name: String, call: FlutterMethodCall) -> T? { private func getArgument<T>(for name: String, call: FlutterMethodCall) -> T? {
guard let data = call.arguments as? Dictionary<String, AnyObject> else { guard let data = call.arguments as? Dictionary<String, AnyObject> else {
+1 -3
View File
@@ -16,8 +16,6 @@ class NearbySession: NSObject {
self.session = MCSession(peer: peerID) self.session = MCSession(peer: peerID)
} }
static let messageReceived = Notification.Name("NearbySessionReceivedMessage")
static func create(peerID: MCPeerID) -> NearbySession { static func create(peerID: MCPeerID) -> NearbySession {
let instance = NearbySession(peerID: peerID) let instance = NearbySession(peerID: peerID)
instance.session.delegate = instance instance.session.delegate = instance
@@ -34,7 +32,7 @@ extension NearbySession: MCSessionDelegate {
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
NotificationCenter.default.post( NotificationCenter.default.post(
name: NearbySession.messageReceived, name: ON_MESSAGE_RECEIVED,
object: nil, object: nil,
userInfo: ["from": peerID, "data": data] userInfo: ["from": peerID, "data": data]
) )
+153 -3
View File
@@ -12,7 +12,21 @@ export 'src/models/models.dart';
export 'src/utils/utils.dart'; export 'src/utils/utils.dart';
export 'src/types/types.dart'; export 'src/types/types.dart';
///
/// The main tool for working with a P2P network.
/// Implementations:
/// * for Android - [NearbyAndroidService]
/// * for IOS - [NearbyIOSService]
///
/// **The plugin is not supported for other platforms yet**
///
abstract class NearbyService { abstract class NearbyService {
///
/// The only way to get an instance of [NearbyService].
///
/// Creates a service suitable for the current platform.
/// Otherwise it throws [NearbyServiceException].
///
static NearbyService getInstance({NearbyServiceLogLevel? logLevel}) { static NearbyService getInstance({NearbyServiceLogLevel? logLevel}) {
if (logLevel != null) { if (logLevel != null) {
Logger.level = logLevel; Logger.level = logLevel;
@@ -31,65 +45,201 @@ abstract class NearbyService {
} }
} }
///
/// Returns [NearbyService] cast as [NearbyIOSService] if the current
/// platform is IOS. Otherwise, returns null.
///
late final NearbyIOSService? ios = get( late final NearbyIOSService? ios = get(
onIOS: (e) => e, onIOS: (e) => e,
); );
///
/// Returns [NearbyService] cast as [NearbyAndroidService] if the current
/// platform is Android. Otherwise, returns null.
///
late final NearbyAndroidService? android = get( late final NearbyAndroidService? android = get(
onAndroid: (e) => e, onAndroid: (e) => e,
); );
ValueListenable<bool> get isCommunicationChannelConnecting; ///
/// **A value to determine the communication channel's status.**
///
/// For **Android** this is the socket connection state.
/// The server can wait for the client to connect,
/// and the client can be waiting for the server to be created.
/// Also, both can be in connected and unconnected states.
///
/// For **IOS** this is the state of the message stream subscription.
/// which is generated for the device with the current connected device ID.
///
ValueListenable<CommunicationChannelState> get communicationChannelState;
///
/// Gets version of current platform.
///
/// * Sample answer for Android: "Android 14"
/// * Sample answer for iOS: "IOS 17.2"
///
Future<String?> getPlatformVersion() { Future<String?> getPlatformVersion() {
return NearbyServicePlatform.instance.getPlatformVersion(); return NearbyServicePlatform.instance.getPlatformVersion();
} }
///
/// Gets model of current device.
///
/// * Sample answer for Android: "Android"
/// * Sample answer for iOS: "IPhone 15 Pro"
///
Future<String?> getPlatformModel() { Future<String?> getPlatformModel() {
return NearbyServicePlatform.instance.getPlatformModel(); return NearbyServicePlatform.instance.getPlatformModel();
} }
Future<NearbyDevice?> getCurrentDevice() { ///
return NearbyServicePlatform.instance.getCurrentDevice(); /// Getting info about the current device in P2P scope.
///
/// This method can be used to define the name
/// of the current device to be displayed on the network to other users.
///
/// Also [NearbyDeviceInfo] contains the connection ID. Note that
/// the ID obtained from [getCurrentDeviceInfo] for Android
/// will always be **02:00:00:00:00:00** for privacy issues.
/// For iOS, it can be safely used.
///
Future<NearbyDeviceInfo?> getCurrentDeviceInfo() {
return NearbyServicePlatform.instance.getCurrentDeviceInfo();
} }
///
/// Since Wi-fi must be enabled to use the plugin in Android,
/// [openServicesSettings] can be used to redirect the user to the **Wi-fi**
/// service settings on Android.
///
/// For iOS it is not necessary to have Wi-fi enabled.
/// In case of its absence, the platform will try to establish a connection
/// by other methods. However, this method will open the settings page
/// for iOS, if you want the user to use Wi-fi.
///
Future<void> openServicesSettings() { Future<void> openServicesSettings() {
return NearbyServicePlatform.instance.openServicesSettings(); return NearbyServicePlatform.instance.openServicesSettings();
} }
///
/// A single retrieval of the current list of devices in a P2P network.
///
/// Returns the list of [NearbyDevice] that have been stored so far.
/// If you want to use a constantly updated list of devices, use [getPeersStream].
///
Future<List<NearbyDevice>> getPeers() { Future<List<NearbyDevice>> getPeers() {
return NearbyServicePlatform.instance.getPeers(); return NearbyServicePlatform.instance.getPeers();
} }
///
/// Returns a constantly updating list of [NearbyDevice] that
/// the platform-specific service has found at each point in time.
///
Stream<List<NearbyDevice>> getPeersStream() { Stream<List<NearbyDevice>> getPeersStream() {
return NearbyServicePlatform.instance.getPeersStream(); return NearbyServicePlatform.instance.getPeersStream();
} }
///
/// Returns the constantly updating [NearbyDevice] you are currently connected to.
/// If it returns null, then there is no connection at the moment.
///
Stream<NearbyDevice?> getConnectedDeviceStream(NearbyDevice device) { Stream<NearbyDevice?> getConnectedDeviceStream(NearbyDevice device) {
return NearbyServicePlatform.instance.getConnectedDeviceStream(device); return NearbyServicePlatform.instance.getConnectedDeviceStream(device);
} }
///
/// Initialization of a platform-specific service.
///
/// The [initialize] method must be called before calling any
/// other getters and methods related to P2P network (all except
/// [getPlatformVersion] and [getPlatformModel]).
///
Future<bool> initialize({ Future<bool> initialize({
NearbyInitializeData data = const NearbyInitializeData(), NearbyInitializeData data = const NearbyInitializeData(),
}); });
///
/// Starts searching for devices using a platform-specific service.
///
/// Note that the [NearbyIOSService] implementation starts **browsing** or
/// **advertising** depending on the [NearbyIOSService.isBrowser].
///
Future<bool> discover(); Future<bool> discover();
///
/// Stops searching for devices using a platform-specific service.
///
/// Note that the [NearbyIOSService] implementation stops **browsing** or
/// **advertising** depending on the [NearbyIOSService.isBrowser].
///
Future<bool> stopDiscovery(); Future<bool> stopDiscovery();
///
/// Connects to passed [device] using a platform-specific service.
///
/// Note that the [NearbyIOSService] implementation **invites** or
/// **accepts invite** depending on the [NearbyIOSService.isBrowser].
///
/// Note that if [Platform.isIOS] == true, [NearbyIOSDevice] should be passed.
/// If [Platform.isAndroid] == true, [NearbyAndroidDevice] should be passed.
///
Future<bool> connect(NearbyDevice device); Future<bool> connect(NearbyDevice device);
///
/// Disconnects from passed [device] using a platform-specific service.
///
/// Note that if [Platform.isIOS] == true, [NearbyIOSDevice] should be passed.
/// If [Platform.isAndroid] == true, [NearbyAndroidDevice] should be passed.
///
Future<bool> disconnect(NearbyDevice device); Future<bool> disconnect(NearbyDevice device);
///
/// If the device is already connected, it does not mean that you can
/// send and receive data.
///
/// There is a separate function for this in [NearbyService] - communication channel.
/// You need to call [startCommunicationChannel] before using [send].
/// A communication channel can only be created if you are connected to some device.
///
/// You can monitor changes in communication channel state using the [communicationChannelState] getter.
///
FutureOr<bool> startCommunicationChannel( FutureOr<bool> startCommunicationChannel(
NearbyCommunicationChannelData data, NearbyCommunicationChannelData data,
); );
///
/// If you called [startCommunicationChannel], remember that you have
/// created a subscription to receive messages.
///
/// Accordingly, it is essential to terminate any subscription.
/// Use [endCommunicationChannel] for this purpose.
///
FutureOr<bool> endCommunicationChannel(); FutureOr<bool> endCommunicationChannel();
///
/// Method to send data to the created communication channel.
///
FutureOr<bool> send(OutgoingNearbyMessage message); FutureOr<bool> send(OutgoingNearbyMessage message);
} }
extension NearbyServiceGetterExtension on NearbyService { extension NearbyServiceGetterExtension on NearbyService {
///
/// If you want to do different actions or get different data
/// **depending on the platform**, use [get].
///
/// * The [onAndroid] callback returns this instance of [NearbyService],
/// cast as [NearbyAndroidService] if [Platform.isAndroid] is true.
///
/// * The [onIOS] callback returns this instance of [NearbyService],
/// cast as [NearbyIOSService] if [Platform.isIOS] is true.
///
/// * The [onAny] callback returns this instance of [NearbyService] with
/// no casting if both [Platform.isAndroid] and [Platform.isIOS] are false.
///
/// **Note: any of the callbacks must not be null!**
///
T? get<T>({ T? get<T>({
T Function(NearbyAndroidService)? onAndroid, T Function(NearbyAndroidService)? onAndroid,
T Function(NearbyIOSService)? onIOS, T Function(NearbyIOSService)? onIOS,
+2 -2
View File
@@ -21,10 +21,10 @@ class MethodChannelNearbyService extends NearbyServicePlatform {
} }
@override @override
Future<NearbyDevice?> getCurrentDevice() async { Future<NearbyDeviceInfo?> getCurrentDeviceInfo() async {
return NearbyDeviceMapper.instance.mapToDevice( return NearbyDeviceMapper.instance.mapToDevice(
await methodChannel.invokeMethod('getCurrentDevice'), await methodChannel.invokeMethod('getCurrentDevice'),
); )?.info;
} }
@override @override
+1 -1
View File
@@ -31,7 +31,7 @@ abstract class NearbyServicePlatform extends PlatformInterface {
throw UnimplementedError('getPlatformModel() has not been implemented.'); throw UnimplementedError('getPlatformModel() has not been implemented.');
} }
Future<NearbyDevice?> getCurrentDevice() { Future<NearbyDeviceInfo?> getCurrentDeviceInfo() {
throw UnimplementedError('getCurrentDevice() has not been implemented.'); throw UnimplementedError('getCurrentDevice() has not been implemented.');
} }
@@ -0,0 +1,5 @@
///
/// The status of the communication channel for data exchange.
/// Use it to determine if you can send data over the communication channel or not.
///
enum CommunicationChannelState { notConnected, loading, connected }
+1
View File
@@ -1,3 +1,4 @@
export 'nearby_device.dart'; export 'nearby_device.dart';
export 'nearby_device_status.dart'; export 'nearby_device_status.dart';
export 'nearby_message.dart'; export 'nearby_message.dart';
export 'communication_channel_state.dart';
+85 -4
View File
@@ -1,15 +1,58 @@
import 'dart:io'; import 'dart:io';
import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/unknown.dart';
///
/// The model of the device found in the P2P network.
///
abstract class NearbyDevice { abstract class NearbyDevice {
static const unknown = 'unknown'; ///
/// The base device contains [info] and [status].
/// These are parameters that devices will have independent of the platform.
///
const NearbyDevice({required this.info, required this.status}); const NearbyDevice({required this.info, required this.status});
///
/// The minimum information about the device required
/// to display it in the list and connect to it.
///
final NearbyDeviceInfo info; final NearbyDeviceInfo info;
///
/// The connection status of the device.
///
final NearbyDeviceStatus status; final NearbyDeviceStatus status;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NearbyDevice &&
runtimeType == other.runtimeType &&
info == other.info &&
status == other.status;
@override
int get hashCode => info.hashCode ^ status.hashCode;
@override
String toString() {
return 'NearbyDevice{info: $info, status: $status}';
}
///
/// If you want to get different data
/// **depending on the platform**, use [byPlatform].
///
/// * The [onAndroid] callback returns this instance of [NearbyDevice],
/// cast as [NearbyAndroidDevice] if [Platform.isAndroid] is true.
///
/// * The [onIOS] callback returns this instance of [NearbyDevice],
/// cast as [NearbyIOSDevice] if [Platform.isIOS] is true.
///
/// * The [onAny] callback returns this instance of [NearbyDevice] with
/// no casting if both [Platform.isAndroid] and [Platform.isIOS] are false.
///
T? byPlatform<T>({ T? byPlatform<T>({
T Function(NearbyDevice)? onAny, T Function(NearbyDevice)? onAny,
T Function(NearbyAndroidDevice)? onAndroid, T Function(NearbyAndroidDevice)? onAndroid,
@@ -25,7 +68,13 @@ abstract class NearbyDevice {
} }
} }
///
/// Converter for devices from JSON to models.
///
abstract interface class NearbyDeviceMapper { abstract interface class NearbyDeviceMapper {
///
/// Get the mapper instance for the current platform.
///
static NearbyDeviceMapper get instance { static NearbyDeviceMapper get instance {
if (Platform.isAndroid) { if (Platform.isAndroid) {
return NearbyAndroidMapper(); return NearbyAndroidMapper();
@@ -39,27 +88,59 @@ abstract interface class NearbyDeviceMapper {
); );
} }
///
/// Converts JSON to a list of [NearbyDevice].
///
List<NearbyDevice> mapToDeviceList(dynamic value); List<NearbyDevice> mapToDeviceList(dynamic value);
///
/// Converts JSON to a [NearbyDevice].
///
NearbyDevice? mapToDevice(dynamic value); NearbyDevice? mapToDevice(dynamic value);
} }
///
/// Minimal information about the device.
///
class NearbyDeviceInfo { class NearbyDeviceInfo {
///
/// Used to connect to the device via [id].
/// Depending on the platform, [id] means different parameters.
///
/// * For Android [id] is the MAC address of the device.
/// * For IOS [id] is the [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) passed from the IOS platform.
///
const NearbyDeviceInfo({ const NearbyDeviceInfo({
required this.displayName, required this.displayName,
required this.id, required this.id,
}); });
///
/// Get [NearbyDeviceInfo] from [Map].
///
factory NearbyDeviceInfo.fromJson(Map<String, dynamic>? json) { factory NearbyDeviceInfo.fromJson(Map<String, dynamic>? json) {
return NearbyDeviceInfo( return NearbyDeviceInfo(
displayName: json?['displayName'] ?? NearbyDevice.unknown, displayName: json?['displayName'] ?? kNearbyUnknown,
id: json?['id'] ?? NearbyDevice.unknown, id: json?['id'],
); );
} }
///
/// The name of the device in the context of a P2P network.
///
final String displayName; final String displayName;
///
/// Depending on the platform, [id] means different parameters.
///
/// * For Android [id] is the MAC address of the device.
/// * For IOS [id] is the [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) passed from the IOS platform.
///
final String id; final String id;
///
/// Get [Map] from [NearbyDeviceInfo].
///
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'id': id, 'id': id,
+36
View File
@@ -1,3 +1,6 @@
///
/// Status of device connection.
///
enum NearbyDeviceStatus { enum NearbyDeviceStatus {
available, available,
connected, connected,
@@ -5,8 +8,36 @@ enum NearbyDeviceStatus {
connecting, connecting,
unavailable; unavailable;
///
/// Checks if status is [NearbyDeviceStatus.connected]
///
bool get isConnected => this == NearbyDeviceStatus.connected; bool get isConnected => this == NearbyDeviceStatus.connected;
///
/// Checks if status is [NearbyDeviceStatus.available]
///
bool get isAvailable => this == NearbyDeviceStatus.available;
///
/// Checks if status is [NearbyDeviceStatus.failed]
///
bool get isFailed => this == NearbyDeviceStatus.failed;
///
/// Checks if status is [NearbyDeviceStatus.connecting]
///
bool get isConnecting => this == NearbyDeviceStatus.connecting;
///
/// Checks if status is [NearbyDeviceStatus.unavailable]
///
bool get isUnavailable => this == NearbyDeviceStatus.unavailable;
///
/// Get [NearbyDeviceStatus] from the Android platform code.
///
/// You can read about it on the [Android developer site](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice#constants_1).
///
static NearbyDeviceStatus fromAndroidCode(num? code) { static NearbyDeviceStatus fromAndroidCode(num? code) {
if (code == null) { if (code == null) {
return NearbyDeviceStatus.failed; return NearbyDeviceStatus.failed;
@@ -21,6 +52,11 @@ enum NearbyDeviceStatus {
}; };
} }
///
/// Get [NearbyDeviceStatus] from the IOS platform code.
///
/// You can read about it on the [IOS developer site](https://developer.apple.com/documentation/multipeerconnectivity/mcsessionstate).
///
static NearbyDeviceStatus fromIosCode(String? code) { static NearbyDeviceStatus fromIosCode(String? code) {
if (code == null) { if (code == null) {
return NearbyDeviceStatus.failed; return NearbyDeviceStatus.failed;
+80 -11
View File
@@ -1,18 +1,54 @@
import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart';
///
/// Basic Message Abstraction.
///
abstract class NearbyMessage { abstract class NearbyMessage {
///
/// The basic message contains only [value] - the content
/// to be sent or received.
///
const NearbyMessage({required this.value}); const NearbyMessage({required this.value});
final String value; final String value;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NearbyMessage &&
runtimeType == other.runtimeType &&
value == other.value;
@override
int get hashCode => value.hashCode;
@override
String toString() {
return 'NearbyMessage{value: $value}';
}
} }
///
/// The message that will be sent from the current device.
///
class OutgoingNearbyMessage extends NearbyMessage { class OutgoingNearbyMessage extends NearbyMessage {
///
/// To send a message, in addition to [value], you need to pass [receiver]
/// to know to whom the message is addressed.
///
const OutgoingNearbyMessage({ const OutgoingNearbyMessage({
required super.value, required super.value,
required this.receiver, required this.receiver,
}); });
///
/// Data of the user to whom the message is addressed.
///
final NearbyDeviceInfo receiver;
///
/// Get [Map] from [OutgoingNearbyMessage].
///
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'message': value, 'message': value,
@@ -20,26 +56,59 @@ class OutgoingNearbyMessage extends NearbyMessage {
}; };
} }
final NearbyDeviceInfo receiver; @override
bool operator ==(Object other) =>
identical(this, other) ||
super == other &&
other is OutgoingNearbyMessage &&
runtimeType == other.runtimeType &&
receiver == other.receiver;
@override
int get hashCode => super.hashCode ^ receiver.hashCode;
} }
///
/// Message received by the current device.
///
class ReceivedNearbyMessage extends NearbyMessage { class ReceivedNearbyMessage extends NearbyMessage {
///
/// The received message contains a [sender] in addition to [value],
/// to know from whom the message came.
///
const ReceivedNearbyMessage({ const ReceivedNearbyMessage({
required super.value, required super.value,
required this.sender, required this.sender,
}); });
///
/// Get [ReceivedNearbyMessage] from [Map].
///
factory ReceivedNearbyMessage.fromJson(Map<String, dynamic>? json) { factory ReceivedNearbyMessage.fromJson(Map<String, dynamic>? json) {
try { return ReceivedNearbyMessage(
return ReceivedNearbyMessage( value: json?['message'] ?? '',
value: json?['message'] ?? '', sender: NearbyDeviceInfo.fromJson(json?['sender']),
sender: NearbyDeviceInfo.fromJson(json?['sender']), );
);
} catch (e) {
Logger.error(e);
throw NearbyServiceException('Can\'t map to device $json');
}
} }
///
/// Data of the user from whom the message came.
///
final NearbyDeviceInfo sender; final NearbyDeviceInfo sender;
@override
bool operator ==(Object other) =>
identical(this, other) ||
super == other &&
other is ReceivedNearbyMessage &&
runtimeType == other.runtimeType &&
sender == other.sender;
@override
int get hashCode => super.hashCode ^ sender.hashCode;
@override
String toString() {
return 'ReceivedNearbyMessage{sender: $sender}';
}
} }
@@ -1,36 +1,83 @@
import 'dart:convert'; import 'package:nearby_service/src/utils/json_decoder.dart';
import 'package:nearby_service/src/utils/unknown.dart';
///
/// The class representing the connection information
/// of a Wi-Fi p2p group connection for Android.
///
class NearbyConnectionAndroidInfo { class NearbyConnectionAndroidInfo {
static const unknown = 'unknown'; ///
/// The class representing the Android class
/// [WifiP2pInfo](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo).
///
const NearbyConnectionAndroidInfo({ const NearbyConnectionAndroidInfo({
required this.ownerIpAddress, required this.ownerIpAddress,
required this.groupFormed, required this.groupFormed,
required this.isGroupOwner, required this.isGroupOwner,
}); });
factory NearbyConnectionAndroidInfo.fromJson(Map<String, dynamic> json) { ///
final ownerIpAddress = (json['groupOwnerAddress'] ?? unknown) as String; /// Get [NearbyConnectionAndroidInfo] from [Map].
///
factory NearbyConnectionAndroidInfo.fromJson(Map<String, dynamic>? json) {
final ownerIpAddress =
(json?['groupOwnerAddress'] ?? kNearbyUnknown) as String;
return NearbyConnectionAndroidInfo( return NearbyConnectionAndroidInfo(
ownerIpAddress: ownerIpAddress.replaceFirst('/', ''), ownerIpAddress: ownerIpAddress.replaceFirst('/', ''),
groupFormed: json['groupFormed'] ?? false, groupFormed: json?['groupFormed'] ?? false,
isGroupOwner: json['isGroupOwner'] ?? false, isGroupOwner: json?['isGroupOwner'] ?? false,
); );
} }
///
/// Group owner address.
/// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo)
///
final String ownerIpAddress; final String ownerIpAddress;
///
/// Indicates if the current device is the group owner.
/// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo)
///
final bool isGroupOwner; final bool isGroupOwner;
///
/// Indicates if the current device is the group owner.
/// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo)
///
final bool groupFormed; final bool groupFormed;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NearbyConnectionAndroidInfo &&
runtimeType == other.runtimeType &&
ownerIpAddress == other.ownerIpAddress &&
isGroupOwner == other.isGroupOwner &&
groupFormed == other.groupFormed;
@override
int get hashCode =>
ownerIpAddress.hashCode ^ isGroupOwner.hashCode ^ groupFormed.hashCode;
@override
String toString() {
return 'NearbyConnectionAndroidInfo{ownerIpAddress: $ownerIpAddress, isGroupOwner: $isGroupOwner, groupFormed: $groupFormed}';
}
} }
/// Mapper from JSON to [NearbyConnectionAndroidInfo]
class NearbyConnectionInfoMapper { class NearbyConnectionInfoMapper {
NearbyConnectionInfoMapper._(); NearbyConnectionInfoMapper._();
///
/// Converts JSON to a [NearbyConnectionAndroidInfo].
///
static NearbyConnectionAndroidInfo? mapToInfo(dynamic value) { static NearbyConnectionAndroidInfo? mapToInfo(dynamic value) {
final jsonValue = jsonDecode(value) as Map<String, dynamic>?; if (value == null) {
if (jsonValue == null) {
return null; return null;
} }
return NearbyConnectionAndroidInfo.fromJson(jsonValue); final decoded = JSONDecoder.decodeMap(value);
return NearbyConnectionAndroidInfo.fromJson(decoded);
} }
} }
@@ -1,7 +1,21 @@
import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/decoder.dart'; import 'package:nearby_service/src/utils/json_decoder.dart';
import 'package:nearby_service/src/utils/unknown.dart';
///
/// A device on a P2P network obtained from the Android platform.
///
class NearbyAndroidDevice extends NearbyDevice { class NearbyAndroidDevice extends NearbyDevice {
///
/// The class representing the Android class
/// [WifiP2pDevice](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice).
///
/// Automatically generates the [info] field from the [deviceName]
/// and [deviceAddress] fields.
///
/// They are used because in a Wifi Direct environment
/// the MAC address of the device is an identifier on the network.
///
NearbyAndroidDevice({ NearbyAndroidDevice({
required String deviceName, required String deviceName,
required super.status, required super.status,
@@ -20,13 +34,16 @@ class NearbyAndroidDevice extends NearbyDevice {
), ),
); );
///
/// Gets [NearbyAndroidDevice] from [Map]
///
factory NearbyAndroidDevice.fromJson(Map<String, dynamic>? json) { factory NearbyAndroidDevice.fromJson(Map<String, dynamic>? json) {
return NearbyAndroidDevice( return NearbyAndroidDevice(
deviceName: json?['deviceName'] ?? NearbyDevice.unknown, deviceName: json?['deviceName'] ?? kNearbyUnknown,
deviceAddress: json?['deviceAddress'] ?? NearbyDevice.unknown, deviceAddress: json?['deviceAddress'] ?? kNearbyUnknown,
isGroupOwner: json?['isGroupOwner'] ?? false, isGroupOwner: json?['isGroupOwner'] ?? false,
isServiceDiscoveryCapable: json?['isServiceDiscoveryCapable'] ?? false, isServiceDiscoveryCapable: json?['isServiceDiscoveryCapable'] ?? false,
primaryDeviceType: json?['primaryDeviceType'] ?? NearbyDevice.unknown, primaryDeviceType: json?['primaryDeviceType'] ?? kNearbyUnknown,
secondaryDeviceType: json?['secondaryDeviceType'], secondaryDeviceType: json?['secondaryDeviceType'],
wpsDisplaySupported: json?['wpsDisplaySupported'] ?? false, wpsDisplaySupported: json?['wpsDisplaySupported'] ?? false,
wpsKeypadSupported: json?['wpsKeypadSupported'] ?? false, wpsKeypadSupported: json?['wpsKeypadSupported'] ?? false,
@@ -35,20 +52,92 @@ class NearbyAndroidDevice extends NearbyDevice {
); );
} }
///
/// The device MAC address uniquely identifies a Wi-Fi p2p device.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final String deviceAddress; final String deviceAddress;
///
/// True if the device is a group owner.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final bool isGroupOwner; final bool isGroupOwner;
///
/// True if the device is capable of service discovery.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final bool isServiceDiscoveryCapable; final bool isServiceDiscoveryCapable;
///
/// Primary device type identifies the type of device.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final String primaryDeviceType; final String primaryDeviceType;
///
/// Secondary device type is an optional attribute.
/// that can be provided by a device in addition to the primary device type.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final String? secondaryDeviceType; final String? secondaryDeviceType;
///
/// True if WPS keypad configuration is supported.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final bool wpsKeypadSupported; final bool wpsKeypadSupported;
///
/// True if WPS push button configuration is supported.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final bool wpsPbcSupported; final bool wpsPbcSupported;
///
/// True if WPS display configuration is supported.
/// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
///
final bool wpsDisplaySupported; final bool wpsDisplaySupported;
@override
bool operator ==(Object other) =>
identical(this, other) ||
super == other &&
other is NearbyAndroidDevice &&
runtimeType == other.runtimeType &&
deviceAddress == other.deviceAddress &&
isGroupOwner == other.isGroupOwner &&
isServiceDiscoveryCapable == other.isServiceDiscoveryCapable &&
primaryDeviceType == other.primaryDeviceType &&
secondaryDeviceType == other.secondaryDeviceType &&
wpsKeypadSupported == other.wpsKeypadSupported &&
wpsPbcSupported == other.wpsPbcSupported &&
wpsDisplaySupported == other.wpsDisplaySupported;
@override
int get hashCode =>
super.hashCode ^
deviceAddress.hashCode ^
isGroupOwner.hashCode ^
isServiceDiscoveryCapable.hashCode ^
primaryDeviceType.hashCode ^
secondaryDeviceType.hashCode ^
wpsKeypadSupported.hashCode ^
wpsPbcSupported.hashCode ^
wpsDisplaySupported.hashCode;
@override
String toString() {
return 'NearbyAndroidDevice{deviceAddress: $deviceAddress, isGroupOwner: $isGroupOwner, isServiceDiscoveryCapable: $isServiceDiscoveryCapable, primaryDeviceType: $primaryDeviceType, secondaryDeviceType: $secondaryDeviceType, wpsKeypadSupported: $wpsKeypadSupported, wpsPbcSupported: $wpsPbcSupported, wpsDisplaySupported: $wpsDisplaySupported}';
}
} }
class NearbyAndroidMapper implements NearbyDeviceMapper { class NearbyAndroidMapper implements NearbyDeviceMapper {
@override @override
List<NearbyDevice> mapToDeviceList(dynamic value) { List<NearbyDevice> mapToDeviceList(dynamic value) {
final decoded = Decoder.decodeList(value); final decoded = JSONDecoder.decodeList(value);
return [ return [
...?decoded?.map( ...?decoded?.map(
(e) => NearbyAndroidDevice.fromJson(e as Map<String, dynamic>?), (e) => NearbyAndroidDevice.fromJson(e as Map<String, dynamic>?),
@@ -58,6 +147,6 @@ class NearbyAndroidMapper implements NearbyDeviceMapper {
@override @override
NearbyDevice? mapToDevice(dynamic value) { NearbyDevice? mapToDevice(dynamic value) {
return NearbyAndroidDevice.fromJson(Decoder.decodeMap(value)); return NearbyAndroidDevice.fromJson(JSONDecoder.decodeMap(value));
} }
} }
@@ -5,18 +5,30 @@ import 'package:nearby_service/nearby_service.dart';
import 'socket_service/nearby_socket_service.dart'; import 'socket_service/nearby_socket_service.dart';
///
/// Android implementation for [NearbyService].
///
/// Uses [NearbyServiceAndroidPlatform] to perform actions.
/// Connects to the device via a socket from [NearbySocketService].
///
class NearbyAndroidService extends NearbyService { class NearbyAndroidService extends NearbyService {
late final _socketService = NearbySocketService(this); late final _socketService = NearbySocketService(this);
@override @override
ValueListenable<bool> get isCommunicationChannelConnecting { ValueListenable<CommunicationChannelState> get communicationChannelState {
return _socketService.isConnecting; return _socketService.state;
} }
NearbyConnectionAndroidInfo? get connectionInfo { NearbyConnectionAndroidInfo? get connectionInfo {
return _socketService.connectionInfo; return _socketService.connectionInfo;
} }
///
/// Initializes Android [WifiP2PManager](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager)
///
/// Starts listening for changes to the P2P network.
/// Adds platform-level action listeners.
///
@override @override
Future<bool> initialize({ Future<bool> initialize({
NearbyInitializeData data = const NearbyInitializeData(), NearbyInitializeData data = const NearbyInitializeData(),
@@ -24,40 +36,48 @@ class NearbyAndroidService extends NearbyService {
return NearbyServiceAndroidPlatform.instance.initialize(); return NearbyServiceAndroidPlatform.instance.initialize();
} }
///
/// Starts discovery of the Wifi Direct network.
///
@override @override
Future<bool> discover() { Future<bool> discover() {
return NearbyServiceAndroidPlatform.instance.discover(); return NearbyServiceAndroidPlatform.instance.discover();
} }
///
/// Stops discovery of the Wifi Direct network.
///
@override @override
Future<bool> stopDiscovery() { Future<bool> stopDiscovery() {
return NearbyServiceAndroidPlatform.instance.stopDiscovery(); return NearbyServiceAndroidPlatform.instance.stopDiscovery();
} }
///
/// Connects to the [device] on the Wifi Direct network.
///
/// Note! Requires [NearbyAndroidDevice] to be passed.
///
@override @override
Future<bool> connect(NearbyDevice device) { Future<bool> connect(NearbyDevice device) {
_requireAndroidDevice(device); _requireAndroidDevice(device);
return NearbyServiceAndroidPlatform.instance.connect(device.info.id); return NearbyServiceAndroidPlatform.instance.connect(device.info.id);
} }
///
/// Disconnects from the [device] on the Wifi Direct network.
///
/// Note! Requires [NearbyAndroidDevice] to be passed.
///
@override @override
Future<bool> disconnect(NearbyDevice device) { Future<bool> disconnect(NearbyDevice device) {
_requireAndroidDevice(device); _requireAndroidDevice(device);
return NearbyServiceAndroidPlatform.instance.disconnect(device.info.id); return NearbyServiceAndroidPlatform.instance.disconnect(device.info.id);
} }
Future<bool> requestPermissions() { ///
return NearbyServiceAndroidPlatform.instance.requestPermissions(); /// Starts a socket service to transfer data. Uses device with
} /// [NearbyCommunicationChannelData.connectedDeviceId].
///
Future<bool> checkWifiService() {
return NearbyServiceAndroidPlatform.instance.checkWifiService();
}
Future<NearbyConnectionAndroidInfo?> getConnectionInfo() {
return NearbyServiceAndroidPlatform.instance.getConnectionInfo();
}
@override @override
FutureOr<bool> startCommunicationChannel( FutureOr<bool> startCommunicationChannel(
NearbyCommunicationChannelData data, NearbyCommunicationChannelData data,
@@ -65,16 +85,51 @@ class NearbyAndroidService extends NearbyService {
return _socketService.startSocket(data: data); return _socketService.startSocket(data: data);
} }
///
/// Ends the socket service to stop transferring data.
///
@override @override
FutureOr<bool> endCommunicationChannel() { FutureOr<bool> endCommunicationChannel() {
return _socketService.cancel(); return _socketService.cancel();
} }
///
/// Adds [OutgoingNearbyMessage] to the socket.
///
@override @override
FutureOr<bool> send(OutgoingNearbyMessage message) { FutureOr<bool> send(OutgoingNearbyMessage message) {
return _socketService.send(message); return _socketService.send(message);
} }
///
/// Request permissions at the platform level.
/// **This is required for Android for using the plugin!**
///
/// For Android APIs less 33 requests `ACCESS_FINE_LOCATION` permission.
///
/// For Android APIs equal to 33 or more, requests `ACCESS_FINE_LOCATION`
/// and `NEARBY_WIFI_DEVICES` permissions.
///
Future<bool> requestPermissions() {
return NearbyServiceAndroidPlatform.instance.requestPermissions();
}
///
/// Checks if Wi-fi is enabled.
/// **Wi-fi must be enabled for Android to use the plugin!**
///
Future<bool> checkWifiService() {
return NearbyServiceAndroidPlatform.instance.checkWifiService();
}
///
/// Returns [NearbyConnectionAndroidInfo] -
/// information about the connection information.
///
Future<NearbyConnectionAndroidInfo?> getConnectionInfo() {
return NearbyServiceAndroidPlatform.instance.getConnectionInfo();
}
void _requireAndroidDevice(NearbyDevice device) { void _requireAndroidDevice(NearbyDevice device) {
assert( assert(
device is NearbyAndroidDevice, device is NearbyAndroidDevice,
@@ -12,6 +12,9 @@ part 'ping_manager.dart';
part 'network.dart'; part 'network.dart';
///
/// A service for creating a communication channel on the Android platform.
///
class NearbySocketService { class NearbySocketService {
NearbySocketService(this._manager); NearbySocketService(this._manager);
@@ -19,17 +22,27 @@ class NearbySocketService {
final _pingManager = NearbySocketPingManager(); final _pingManager = NearbySocketPingManager();
final _network = NearbyServiceNetwork(); final _network = NearbyServiceNetwork();
final isConnecting = ValueNotifier(false); final state = ValueNotifier(CommunicationChannelState.notConnected);
NearbyConnectionAndroidInfo? connectionInfo; NearbyConnectionAndroidInfo? connectionInfo;
String? _connectedDeviceId; String? _connectedDeviceId;
WebSocket? _socket; WebSocket? _socket;
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription; StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
///
/// Start a socket with the user's role defined.
/// If he is the owner of the group, he becomes a server.
/// Otherwise, he becomes a client.
///
/// * The server starts up and waits for a request from the
/// client to establish a connection.
/// * The client pings the server until he receives a pong.
/// When he does, he tries to connect to the server.
///
Future<bool> startSocket({ Future<bool> startSocket({
required NearbyCommunicationChannelData data, required NearbyCommunicationChannelData data,
}) async { }) async {
isConnecting.value = true; state.value = CommunicationChannelState.loading;
_connectedDeviceId = data.connectedDeviceId; _connectedDeviceId = data.connectedDeviceId;
connectionInfo = await _manager.getConnectionInfo(); connectionInfo = await _manager.getConnectionInfo();
if (connectionInfo != null && connectionInfo!.groupFormed) { if (connectionInfo != null && connectionInfo!.groupFormed) {
@@ -55,15 +68,18 @@ class NearbySocketService {
return false; return false;
} }
///
/// Add [OutgoingNearbyMessage]'s JSON representation to [_socket].
///
Future<bool> send(OutgoingNearbyMessage message) async { Future<bool> send(OutgoingNearbyMessage message) async {
if (_socket != null && message.receiver.id == _connectedDeviceId) { if (_socket != null && message.receiver.id == _connectedDeviceId) {
final sender = await _manager.getCurrentDevice(); final sender = await _manager.getCurrentDeviceInfo();
if (sender != null) { if (sender != null) {
_socket!.add( _socket!.add(
jsonEncode( jsonEncode(
{ {
'message': message.value, 'message': message.value,
'sender': sender.info.toJson(), 'sender': sender.toJson(),
}, },
), ),
); );
@@ -73,9 +89,11 @@ class NearbySocketService {
return false; return false;
} }
///
/// Turns off [_messagesSubscription] and [_socket].
///
Future<bool> cancel() async { Future<bool> cancel() async {
try { try {
isConnecting.value = false;
await _messagesSubscription?.cancel(); await _messagesSubscription?.cancel();
_messagesSubscription = null; _messagesSubscription = null;
_socket = null; _socket = null;
@@ -153,7 +171,7 @@ class NearbySocketService {
void _createSocketSubscription(NearbyServiceStreamListener socketListener) { void _createSocketSubscription(NearbyServiceStreamListener socketListener) {
Logger.debug('Starting socket subscription'); Logger.debug('Starting socket subscription');
isConnecting.value = false;
if (_connectedDeviceId != null) { if (_connectedDeviceId != null) {
_messagesSubscription = _socket _messagesSubscription = _socket
?.map(MessagesStreamMapper.toMessage) ?.map(MessagesStreamMapper.toMessage)
@@ -162,17 +180,24 @@ class NearbySocketService {
.map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!)) .map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!))
.listen( .listen(
socketListener.onData, socketListener.onData,
onDone: socketListener.onDone, onDone: () {
state.value = CommunicationChannelState.notConnected;
socketListener.onDone?.call();
},
onError: (e, s) { onError: (e, s) {
Logger.error(e); Logger.error(e);
state.value = CommunicationChannelState.notConnected;
socketListener.onError?.call(e, s); socketListener.onError?.call(e, s);
}, },
cancelOnError: socketListener.cancelOnError, cancelOnError: socketListener.cancelOnError,
); );
} }
if (_messagesSubscription != null) { if (_messagesSubscription != null) {
state.value = CommunicationChannelState.connected;
Logger.info('Socket subscription was created successfully'); Logger.info('Socket subscription was created successfully');
socketListener.onCreated?.call(_messagesSubscription!); socketListener.onCreated?.call(_messagesSubscription!);
} else {
state.value = CommunicationChannelState.notConnected;
} }
} }
} }
@@ -1,7 +1,18 @@
import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/decoder.dart'; import 'package:nearby_service/src/utils/json_decoder.dart';
///
/// A device on a P2P network obtained from the IOS platform.
///
class NearbyIOSDevice extends NearbyDevice { class NearbyIOSDevice extends NearbyDevice {
///
/// A class representing an IOS device on a P2P network.
///
/// [NearbyDeviceInfo] for IOS consists of the [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) passed from the platform
/// and a displayName passed from the platform.
///
/// [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) is an identifier on the local network for IOS.
///
NearbyIOSDevice({ NearbyIOSDevice({
required super.info, required super.info,
required super.status, required super.status,
@@ -10,6 +21,9 @@ class NearbyIOSDevice extends NearbyDevice {
this.deviceType, this.deviceType,
}); });
///
/// Gets [NearbyIOSDevice] from [Map].
///
factory NearbyIOSDevice.fromJson(Map<String, dynamic>? json) { factory NearbyIOSDevice.fromJson(Map<String, dynamic>? json) {
return NearbyIOSDevice( return NearbyIOSDevice(
info: NearbyDeviceInfo.fromJson(json), info: NearbyDeviceInfo.fromJson(json),
@@ -20,15 +34,45 @@ class NearbyIOSDevice extends NearbyDevice {
); );
} }
///
/// `UIDevice.current.systemName` from IOS Platform.
///
final String? os; final String? os;
///
/// `UIDevice.current.systemVersion` from IOS Platform.
///
final String? osVersion; final String? osVersion;
///
/// `UIDevice.current.model` from IOS Platform.
///
final String? deviceType; final String? deviceType;
@override
bool operator ==(Object other) =>
identical(this, other) ||
super == other &&
other is NearbyIOSDevice &&
runtimeType == other.runtimeType &&
os == other.os &&
osVersion == other.osVersion &&
deviceType == other.deviceType;
@override
int get hashCode =>
super.hashCode ^ os.hashCode ^ osVersion.hashCode ^ deviceType.hashCode;
@override
String toString() {
return 'NearbyIOSDevice{os: $os, osVersion: $osVersion, deviceType: $deviceType}';
}
} }
class NearbyIOSMapper implements NearbyDeviceMapper { class NearbyIOSMapper implements NearbyDeviceMapper {
@override @override
List<NearbyDevice> mapToDeviceList(dynamic value) { List<NearbyDevice> mapToDeviceList(dynamic value) {
final decoded = Decoder.decodeList(value); final decoded = JSONDecoder.decodeList(value);
return [ return [
...?decoded?.map( ...?decoded?.map(
(e) => NearbyIOSDevice.fromJson(e as Map<String, dynamic>?), (e) => NearbyIOSDevice.fromJson(e as Map<String, dynamic>?),
@@ -39,7 +83,7 @@ class NearbyIOSMapper implements NearbyDeviceMapper {
@override @override
NearbyDevice? mapToDevice(dynamic value) { NearbyDevice? mapToDevice(dynamic value) {
return NearbyIOSDevice.fromJson( return NearbyIOSDevice.fromJson(
Decoder.decodeMap(value), JSONDecoder.decodeMap(value),
); );
} }
} }
+93 -6
View File
@@ -5,22 +5,54 @@ import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart'; import 'package:nearby_service/src/utils/logger.dart';
import 'package:nearby_service/src/utils/stream_mapper.dart'; import 'package:nearby_service/src/utils/stream_mapper.dart';
///
/// IOS implementation for [NearbyService].
///
/// Uses [NearbyServiceIOSPlatform] to perform actions.
/// Connects to the device by subscribing to messages from the selected
/// device by identifier.
///
class NearbyIOSService extends NearbyService { class NearbyIOSService extends NearbyService {
final _isBrowser = ValueNotifier<bool>(true); final _isBrowser = ValueNotifier<bool>(true);
final _isCommunicationChannelConnecting = ValueNotifier<bool>(false); final _state = ValueNotifier(CommunicationChannelState.notConnected);
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription; StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
@override @override
ValueListenable<bool> get isCommunicationChannelConnecting => ValueListenable<CommunicationChannelState> get communicationChannelState =>
_isCommunicationChannelConnecting; _state;
///
/// Determines whether the current device is a **Browser** or **Advertiser**.
///
/// * Browser will only see devices with Advertiser status in the peers list.
/// Browser sends connection requests.
/// * Advertiser will see in the peers list only devices with Browser
/// status that have sent it a connection request.
/// Advertiser accepts or rejects connection requests.
///
ValueListenable<bool> get isBrowser => _isBrowser; ValueListenable<bool> get isBrowser => _isBrowser;
String get _currentConnectionType { String get _currentConnectionType {
return _isBrowser.value ? 'browsing' : 'advertising'; return _isBrowser.value ? 'browsing' : 'advertising';
} }
///
/// Initializes [MCNearbyServiceAdvertiser](https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyserviceadvertiser)
/// and [MCNearbyServiceBrowser](https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyservicebrowser)
/// to allow this device to be both.
///
/// Creates [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) for
/// this device.
///
/// The name of the device on the network can be
/// specified on initialization via the parameter [data].
///
/// [NearbyInitializeData.iosDeviceName] will be passed to the platform as initial
/// name. If a new name is not passed, the previous name stored
/// in [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults)
/// will be used. If there is no saved name, `UIDevice.current.name` will be used.
///
@override @override
Future<bool> initialize({ Future<bool> initialize({
NearbyInitializeData data = const NearbyInitializeData(), NearbyInitializeData data = const NearbyInitializeData(),
@@ -37,6 +69,12 @@ class NearbyIOSService extends NearbyService {
return result; return result;
} }
///
/// Starts discovery on the local P2P network.
///
/// Starts browsing for peers if [isBrowser] is true.
/// Starts advertising for peers if [isBrowser] is false.
///
@override @override
Future<bool> discover() async { Future<bool> discover() async {
final result = _isBrowser.value final result = _isBrowser.value
@@ -50,6 +88,12 @@ class NearbyIOSService extends NearbyService {
return result; return result;
} }
///
/// Slops discovery on the local P2P network.
///
/// Slops browsing for peers if [isBrowser] is true.
/// Slops advertising for peers if [isBrowser] is false.
///
@override @override
Future<bool> stopDiscovery() async { Future<bool> stopDiscovery() async {
final result = _isBrowser.value final result = _isBrowser.value
@@ -64,6 +108,14 @@ class NearbyIOSService extends NearbyService {
return result; return result;
} }
///
/// Connects to the [device] on the P2P network.
///
/// Invites [device] if [isBrowser] is true.
/// Accepts invite from [device] if [isBrowser] is false.
///
/// Note! Requires [NearbyIOSDevice] to be passed.
///
@override @override
Future<bool> connect(NearbyDevice device) async { Future<bool> connect(NearbyDevice device) async {
_requireIOSDevice(device); _requireIOSDevice(device);
@@ -81,6 +133,11 @@ class NearbyIOSService extends NearbyService {
return result; return result;
} }
///
/// Disconnects from the [device] on the P2P network.
///
/// Note! Requires [NearbyIOSDevice] to be passed.
///
@override @override
Future<bool> disconnect(NearbyDevice device) async { Future<bool> disconnect(NearbyDevice device) async {
_requireIOSDevice(device); _requireIOSDevice(device);
@@ -95,12 +152,16 @@ class NearbyIOSService extends NearbyService {
return result; return result;
} }
///
/// Starts listening for messages from device with
/// [NearbyCommunicationChannelData.connectedDeviceId].
///
@override @override
FutureOr<bool> startCommunicationChannel( FutureOr<bool> startCommunicationChannel(
NearbyCommunicationChannelData data, NearbyCommunicationChannelData data,
) async { ) async {
Logger.debug('Creating messages subscription'); Logger.debug('Creating messages subscription');
_isCommunicationChannelConnecting.value = true; _state.value = CommunicationChannelState.loading;
await endCommunicationChannel(); await endCommunicationChannel();
final eventListener = data.eventListener; final eventListener = data.eventListener;
_messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream _messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream
@@ -110,9 +171,13 @@ class NearbyIOSService extends NearbyService {
.cast<ReceivedNearbyMessage>() .cast<ReceivedNearbyMessage>()
.listen( .listen(
eventListener.onData, eventListener.onData,
onDone: eventListener.onDone, onDone: () {
_state.value = CommunicationChannelState.notConnected;
eventListener.onDone?.call();
},
onError: (e, s) { onError: (e, s) {
Logger.error(e); Logger.error(e);
_state.value = CommunicationChannelState.notConnected;
eventListener.onError?.call(e, s); eventListener.onError?.call(e, s);
}, },
cancelOnError: eventListener.cancelOnError, cancelOnError: eventListener.cancelOnError,
@@ -120,11 +185,18 @@ class NearbyIOSService extends NearbyService {
if (_messagesSubscription != null) { if (_messagesSubscription != null) {
Logger.info('Messages subscription was created successfully'); Logger.info('Messages subscription was created successfully');
eventListener.onCreated?.call(_messagesSubscription!); eventListener.onCreated?.call(_messagesSubscription!);
_state.value = CommunicationChannelState.connected;
} else {
_state.value = CommunicationChannelState.notConnected;
} }
_isCommunicationChannelConnecting.value = false;
return true; return true;
} }
///
/// Stops listening for messages from previously passed device with
/// [NearbyCommunicationChannelData.connectedDeviceId].
///
@override @override
FutureOr<bool> endCommunicationChannel() async { FutureOr<bool> endCommunicationChannel() async {
await _messagesSubscription?.cancel(); await _messagesSubscription?.cancel();
@@ -133,15 +205,30 @@ class NearbyIOSService extends NearbyService {
return true; return true;
} }
///
/// Sends [OutgoingNearbyMessage] to [OutgoingNearbyMessage.receiver] via
/// IOS platform.
///
@override @override
Future<bool> send(OutgoingNearbyMessage message) { Future<bool> send(OutgoingNearbyMessage message) {
return NearbyServiceIOSPlatform.instance.send(message); return NearbyServiceIOSPlatform.instance.send(message);
} }
///
/// If you want to ask the user to change the name on the network,
/// you can retrieve the name previously saved in
/// [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) using this method.
///
/// Changing the name on the network is only available for IOS,
/// so the [NearbyIOSService] only can be used for that.
///
Future<String?> getSavedDeviceName() { Future<String?> getSavedDeviceName() {
return NearbyServiceIOSPlatform.instance.getSavedDeviceName(); return NearbyServiceIOSPlatform.instance.getSavedDeviceName();
} }
///
/// Changes the [isBrowser] to the passed [value].
///
void setIsBrowser({required bool value}) { void setIsBrowser({required bool value}) {
Logger.debug('Is Browser Value was set to $value'); Logger.debug('Is Browser Value was set to $value');
_isBrowser.value = value; _isBrowser.value = value;
+77 -1
View File
@@ -3,26 +3,102 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service/nearby_service.dart';
class NearbyCommunicationChannelData<T> { ///
/// A class for creating a communication channel.
///
class NearbyCommunicationChannelData {
///
/// Contains [connectedDeviceId] of the device to be connected
/// to and additional data.
///
/// Since Android connection is more customizable,
/// additional data [androidData] is created for it.
///
const NearbyCommunicationChannelData( const NearbyCommunicationChannelData(
this.connectedDeviceId, { this.connectedDeviceId, {
required this.eventListener, required this.eventListener,
this.androidData = const NearbyAndroidCommunicationChannelData(), this.androidData = const NearbyAndroidCommunicationChannelData(),
}); });
///
/// Identifier of the device to be connected to.
///
final String connectedDeviceId; final String connectedDeviceId;
///
/// Listener for message stream changes.
///
final NearbyServiceStreamListener<ReceivedNearbyMessage> eventListener; final NearbyServiceStreamListener<ReceivedNearbyMessage> eventListener;
///
/// Android-specific connection data.
///
final NearbyAndroidCommunicationChannelData androidData; final NearbyAndroidCommunicationChannelData androidData;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NearbyCommunicationChannelData &&
runtimeType == other.runtimeType &&
connectedDeviceId == other.connectedDeviceId &&
eventListener == other.eventListener &&
androidData == other.androidData;
@override
int get hashCode =>
connectedDeviceId.hashCode ^
eventListener.hashCode ^
androidData.hashCode;
@override
String toString() {
return 'NearbyCommunicationChannelData{connectedDeviceId: $connectedDeviceId, eventListener: $eventListener, androidData: $androidData}';
}
} }
///
/// Android-specific connection data.
///
class NearbyAndroidCommunicationChannelData { class NearbyAndroidCommunicationChannelData {
///
/// By default, no data needs to be passed, all values are already set.
/// This class is used to customize connection of server to client or client to server.
///
const NearbyAndroidCommunicationChannelData({ const NearbyAndroidCommunicationChannelData({
this.clientReconnectInterval = const Duration(seconds: 5), this.clientReconnectInterval = const Duration(seconds: 5),
this.serverListener, this.serverListener,
this.port = 4045, this.port = 4045,
}); });
///
/// The interval at which the client will ping the server
/// while waiting for it to be created.
///
final Duration clientReconnectInterval; final Duration clientReconnectInterval;
///
/// Listener of events that come to the server.
///
final ValueChanged<HttpRequest>? serverListener; final ValueChanged<HttpRequest>? serverListener;
final int port; final int port;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NearbyAndroidCommunicationChannelData &&
runtimeType == other.runtimeType &&
clientReconnectInterval == other.clientReconnectInterval &&
serverListener == other.serverListener &&
port == other.port;
@override
int get hashCode =>
clientReconnectInterval.hashCode ^
serverListener.hashCode ^
port.hashCode;
@override
String toString() {
return 'NearbyAndroidCommunicationChannelData{clientReconnectInterval: $clientReconnectInterval, serverListener: $serverListener, port: $port}';
}
} }
+12
View File
@@ -1,5 +1,17 @@
///
/// Data for plugin initialization.
///
class NearbyInitializeData { class NearbyInitializeData {
///
/// By default, it does not require any data.
///
/// There is an option to pass the device name to IOS [iosDeviceName].
/// For Android platform changing device name in P2P network is not supported.
///
const NearbyInitializeData({this.iosDeviceName}); const NearbyInitializeData({this.iosDeviceName});
///
/// The device name for IOS on the P2P network.
///
final String? iosDeviceName; final String? iosDeviceName;
} }
@@ -2,7 +2,14 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
///
/// Stream Subscription Listener.
///
class NearbyServiceStreamListener<T> { class NearbyServiceStreamListener<T> {
///
/// It is required to pass the [onData] parameter to process the
/// data that came through the stream.
///
const NearbyServiceStreamListener({ const NearbyServiceStreamListener({
required this.onData, required this.onData,
this.onCreated, this.onCreated,
+11
View File
@@ -2,17 +2,28 @@ import 'dart:io';
import 'package:nearby_service/src/utils/logger.dart'; import 'package:nearby_service/src/utils/logger.dart';
///
/// Nearby Service Plugin Exception.
///
/// Indicates what problem occurred in the plugin operation.
///
class NearbyServiceException implements Exception { class NearbyServiceException implements Exception {
NearbyServiceException(this.error) { NearbyServiceException(this.error) {
Logger.error(error); Logger.error(error);
} }
///
/// A call from an unsupported platform.
///
factory NearbyServiceException.unsupportedPlatform({required String caller}) { factory NearbyServiceException.unsupportedPlatform({required String caller}) {
return NearbyServiceException( return NearbyServiceException(
'$caller is not supported for platform ${Platform.operatingSystem}', '$caller is not supported for platform ${Platform.operatingSystem}',
); );
} }
///
/// A decoding error.
///
factory NearbyServiceException.unsupportedDecoding(dynamic value) { factory NearbyServiceException.unsupportedDecoding(dynamic value) {
return NearbyServiceException( return NearbyServiceException(
'Got unknown value=$value with runtimeType=${value.runtimeType}', 'Got unknown value=$value with runtimeType=${value.runtimeType}',
@@ -2,8 +2,8 @@ import 'dart:convert';
import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service/nearby_service.dart';
class Decoder { class JSONDecoder {
Decoder._(); JSONDecoder._();
static Map<String, dynamic>? decodeMap(dynamic value) { static Map<String, dynamic>? decodeMap(dynamic value) {
if (value == null) { if (value == null) {
+8
View File
@@ -1,3 +1,11 @@
///
/// Determines what level of logging the plugin will use.
///
/// * [debug] - Display all logs, including debugging operations logs.
/// * [info] - Display information logs and errors.
/// * [error] - Display only errors.
/// * [disabled] - Do not display any logs.
///
enum NearbyServiceLogLevel { enum NearbyServiceLogLevel {
debug, debug,
info, info,
+2 -2
View File
@@ -1,5 +1,5 @@
import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/decoder.dart'; import 'package:nearby_service/src/utils/json_decoder.dart';
import 'logger.dart'; import 'logger.dart';
@@ -19,7 +19,7 @@ abstract class MessagesStreamMapper {
static ReceivedNearbyMessage? toMessage(dynamic event) { static ReceivedNearbyMessage? toMessage(dynamic event) {
try { try {
final decoded = Decoder.decodeMap(event); final decoded = JSONDecoder.decodeMap(event);
return ReceivedNearbyMessage.fromJson(decoded); return ReceivedNearbyMessage.fromJson(decoded);
} catch (e) { } catch (e) {
Logger.debug( Logger.debug(
+1
View File
@@ -0,0 +1 @@
const kNearbyUnknown = 'unknown';
+1 -1
View File
@@ -50,7 +50,7 @@ class MockNearbyServicePlatform
} }
@override @override
Future<NearbyDevice?> getCurrentDevice() { Future<NearbyDeviceInfo?> getCurrentDeviceInfo() {
// TODO: implement getCurrentDevice // TODO: implement getCurrentDevice
throw UnimplementedError(); throw UnimplementedError();
} }