From 880cded058be0bef01c482f3e17595f573f84d90 Mon Sep 17 00:00:00 2001 From: ksenia312 Date: Wed, 31 Jan 2024 22:21:52 +0100 Subject: [PATCH] doc(lib): add documentation for the dart side --- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/lib/main.dart | 29 ++-- ios/Classes/MyDeviceDataGenerator.swift | 1 + ios/Classes/NearbyServicePlugin.swift | 16 +- ios/Classes/NearbySession.swift | 4 +- lib/nearby_service.dart | 156 +++++++++++++++++- lib/nearby_service_method_channel.dart | 4 +- lib/nearby_service_platform_interface.dart | 2 +- .../models/communication_channel_state.dart | 5 + lib/src/models/models.dart | 1 + lib/src/models/nearby_device.dart | 89 +++++++++- lib/src/models/nearby_device_status.dart | 36 ++++ lib/src/models/nearby_message.dart | 91 ++++++++-- .../models/nearby_connection_info.dart | 67 ++++++-- .../android/models/nearby_device.dart | 101 +++++++++++- .../android/nearby_android_service.dart | 83 ++++++++-- .../socket_service/nearby_socket_service.dart | 39 ++++- .../platforms/ios/models/nearby_device.dart | 50 +++++- lib/src/platforms/ios/nearby_ios_service.dart | 99 ++++++++++- lib/src/types/communication_channel_data.dart | 78 ++++++++- lib/src/types/initialize_data.dart | 12 ++ .../types/nearby_service_stream_listener.dart | 7 + lib/src/utils/exception.dart | 11 ++ .../utils/{decoder.dart => json_decoder.dart} | 4 +- lib/src/utils/log_level.dart | 8 + lib/src/utils/stream_mapper.dart | 4 +- lib/src/utils/unknown.dart | 1 + test/nearby_service_test.dart | 2 +- 28 files changed, 905 insertions(+), 97 deletions(-) create mode 100644 lib/src/models/communication_channel_state.dart rename lib/src/utils/{decoder.dart => json_decoder.dart} (94%) create mode 100644 lib/src/utils/unknown.dart diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9625e10..7c56964 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/example/lib/main.dart b/example/lib/main.dart index 0cbdea6..06bfeeb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -51,15 +51,18 @@ class MyApp extends StatelessWidget { 'Platform: ${service.platformVersion}\n' 'Model: ${service.platformModel}', ), - if (service.currentDevice != null) + if (service.currentDeviceInfo != null) Text( - 'Device Name: ${service.currentDevice!.displayName}\n' - '${Platform.isIOS ? 'Device ID: ${service.currentDevice!.id}' : ''}\n', + 'Device Name: ${service.currentDeviceInfo!.displayName}\n' + '${Platform.isIOS ? 'Device ID: ${service.currentDeviceInfo!.id}' : ''}', ), if (Platform.isIOS) 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 { - final _nearbyService = NearbyService.getInstance(); + late final _nearbyService = NearbyService.getInstance() + ..communicationChannelState.addListener(notifyListeners); AppState state = AppState.idle; List? peers; NearbyDevice? connectedDevice; - NearbyDeviceInfo? currentDevice; + NearbyDeviceInfo? currentDeviceInfo; String platformVersion = 'Unknown'; String platformModel = 'Unknown'; @@ -183,8 +187,8 @@ class AppService extends ChangeNotifier { StreamSubscription? peersSubscription; StreamSubscription? connectedDeviceSubscription; - bool get isCommunicationChannelConnecting { - return _nearbyService.isCommunicationChannelConnecting.value; + CommunicationChannelState get communicationChannelState { + return _nearbyService.communicationChannelState.value; } bool get isIOSBrowser { @@ -202,7 +206,7 @@ class AppService extends ChangeNotifier { notifyListeners(); } - Future getSavedDeviceName() async { + Future getSavedIOSDeviceName() async { return (await _nearbyService.ios?.getSavedDeviceName()) ?? platformModel; } @@ -211,7 +215,7 @@ class AppService extends ChangeNotifier { await _nearbyService.initialize( data: NearbyInitializeData(iosDeviceName: iosDeviceName), ); - currentDevice = (await _nearbyService.getCurrentDevice())?.info; + currentDeviceInfo = await _nearbyService.getCurrentDeviceInfo(); updateState( Platform.isAndroid ? AppState.permissions : AppState.selectClientType, ); @@ -432,7 +436,7 @@ class _IdleBodyState extends State<_IdleBody> { @override void initState() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - context.read().getSavedDeviceName().then((value) { + context.read().getSavedIOSDeviceName().then((value) { controller.text = value; controller.selection = TextSelection.collapsed(offset: value.length); setState(() { @@ -719,7 +723,8 @@ class _ConnectedBody extends StatelessWidget { else _DevicePreview(device: device, largeView: true), const SizedBox(height: 10), - if (!(service.isCommunicationChannelConnecting)) + if (service.communicationChannelState != + CommunicationChannelState.loading) _ActionButton( onTap: () => service.startCommunicationChannel( listener: (event) => AppShackBar.show( diff --git a/ios/Classes/MyDeviceDataGenerator.swift b/ios/Classes/MyDeviceDataGenerator.swift index 0346487..592a451 100644 --- a/ios/Classes/MyDeviceDataGenerator.swift +++ b/ios/Classes/MyDeviceDataGenerator.swift @@ -4,6 +4,7 @@ import MultipeerConnectivity let SERVICE_TYPE = "mp-connection" let PEER_ID = "PEER-ID" let DEVICE_NAME = "DEVICE-NAME" +let ON_MESSAGE_RECEIVED = Notification.Name("NearbySessionOnMessageReceived") class MyDeviceDataGenerator { static func generate(name: String?) -> NearbyDevice { diff --git a/ios/Classes/NearbyServicePlugin.swift b/ios/Classes/NearbyServicePlugin.swift index 60829af..8a03528 100644 --- a/ios/Classes/NearbyServicePlugin.swift +++ b/ios/Classes/NearbyServicePlugin.swift @@ -38,8 +38,8 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin { NotificationCenter.default.addObserver( instance, - selector: #selector(messageReceived), - name: NearbySession.messageReceived, + selector: #selector(onMessageReceived), + name: ON_MESSAGE_RECEIVED, object: nil ) @@ -49,12 +49,6 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin { 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) { @@ -124,6 +118,12 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin { 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(for name: String, call: FlutterMethodCall) -> T? { guard let data = call.arguments as? Dictionary else { diff --git a/ios/Classes/NearbySession.swift b/ios/Classes/NearbySession.swift index 10138ba..35953d8 100644 --- a/ios/Classes/NearbySession.swift +++ b/ios/Classes/NearbySession.swift @@ -16,8 +16,6 @@ class NearbySession: NSObject { self.session = MCSession(peer: peerID) } - static let messageReceived = Notification.Name("NearbySessionReceivedMessage") - static func create(peerID: MCPeerID) -> NearbySession { let instance = NearbySession(peerID: peerID) instance.session.delegate = instance @@ -34,7 +32,7 @@ extension NearbySession: MCSessionDelegate { func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { NotificationCenter.default.post( - name: NearbySession.messageReceived, + name: ON_MESSAGE_RECEIVED, object: nil, userInfo: ["from": peerID, "data": data] ) diff --git a/lib/nearby_service.dart b/lib/nearby_service.dart index 6ad26a1..0d63a99 100644 --- a/lib/nearby_service.dart +++ b/lib/nearby_service.dart @@ -12,7 +12,21 @@ export 'src/models/models.dart'; export 'src/utils/utils.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 { + /// + /// 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}) { if (logLevel != null) { 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( onIOS: (e) => e, ); + + /// + /// Returns [NearbyService] cast as [NearbyAndroidService] if the current + /// platform is Android. Otherwise, returns null. + /// late final NearbyAndroidService? android = get( onAndroid: (e) => e, ); - ValueListenable 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 get communicationChannelState; + /// + /// Gets version of current platform. + /// + /// * Sample answer for Android: "Android 14" + /// * Sample answer for iOS: "IOS 17.2" + /// Future getPlatformVersion() { return NearbyServicePlatform.instance.getPlatformVersion(); } + /// + /// Gets model of current device. + /// + /// * Sample answer for Android: "Android" + /// * Sample answer for iOS: "IPhone 15 Pro" + /// Future getPlatformModel() { return NearbyServicePlatform.instance.getPlatformModel(); } - Future 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 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 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> 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> 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 getConnectedDeviceStream(NearbyDevice 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 initialize({ 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 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 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 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 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 startCommunicationChannel( 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 endCommunicationChannel(); + /// + /// Method to send data to the created communication channel. + /// FutureOr send(OutgoingNearbyMessage message); } 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 Function(NearbyAndroidService)? onAndroid, T Function(NearbyIOSService)? onIOS, diff --git a/lib/nearby_service_method_channel.dart b/lib/nearby_service_method_channel.dart index def4f6e..e58a978 100644 --- a/lib/nearby_service_method_channel.dart +++ b/lib/nearby_service_method_channel.dart @@ -21,10 +21,10 @@ class MethodChannelNearbyService extends NearbyServicePlatform { } @override - Future getCurrentDevice() async { + Future getCurrentDeviceInfo() async { return NearbyDeviceMapper.instance.mapToDevice( await methodChannel.invokeMethod('getCurrentDevice'), - ); + )?.info; } @override diff --git a/lib/nearby_service_platform_interface.dart b/lib/nearby_service_platform_interface.dart index c41b5ae..046ef66 100644 --- a/lib/nearby_service_platform_interface.dart +++ b/lib/nearby_service_platform_interface.dart @@ -31,7 +31,7 @@ abstract class NearbyServicePlatform extends PlatformInterface { throw UnimplementedError('getPlatformModel() has not been implemented.'); } - Future getCurrentDevice() { + Future getCurrentDeviceInfo() { throw UnimplementedError('getCurrentDevice() has not been implemented.'); } diff --git a/lib/src/models/communication_channel_state.dart b/lib/src/models/communication_channel_state.dart new file mode 100644 index 0000000..7bfd5a5 --- /dev/null +++ b/lib/src/models/communication_channel_state.dart @@ -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 } diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index f38f5d7..f8a24de 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,3 +1,4 @@ export 'nearby_device.dart'; export 'nearby_device_status.dart'; export 'nearby_message.dart'; +export 'communication_channel_state.dart'; diff --git a/lib/src/models/nearby_device.dart b/lib/src/models/nearby_device.dart index e3cbb72..9566276 100644 --- a/lib/src/models/nearby_device.dart +++ b/lib/src/models/nearby_device.dart @@ -1,15 +1,58 @@ import 'dart:io'; 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 { - 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}); + /// + /// The minimum information about the device required + /// to display it in the list and connect to it. + /// final NearbyDeviceInfo info; + + /// + /// The connection status of the device. + /// 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 Function(NearbyDevice)? onAny, T Function(NearbyAndroidDevice)? onAndroid, @@ -25,7 +68,13 @@ abstract class NearbyDevice { } } +/// +/// Converter for devices from JSON to models. +/// abstract interface class NearbyDeviceMapper { + /// + /// Get the mapper instance for the current platform. + /// static NearbyDeviceMapper get instance { if (Platform.isAndroid) { return NearbyAndroidMapper(); @@ -39,27 +88,59 @@ abstract interface class NearbyDeviceMapper { ); } + /// + /// Converts JSON to a list of [NearbyDevice]. + /// List mapToDeviceList(dynamic value); + /// + /// Converts JSON to a [NearbyDevice]. + /// NearbyDevice? mapToDevice(dynamic value); } +/// +/// Minimal information about the device. +/// 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({ required this.displayName, required this.id, }); + /// + /// Get [NearbyDeviceInfo] from [Map]. + /// factory NearbyDeviceInfo.fromJson(Map? json) { return NearbyDeviceInfo( - displayName: json?['displayName'] ?? NearbyDevice.unknown, - id: json?['id'] ?? NearbyDevice.unknown, + displayName: json?['displayName'] ?? kNearbyUnknown, + id: json?['id'], ); } + /// + /// The name of the device in the context of a P2P network. + /// 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; + /// + /// Get [Map] from [NearbyDeviceInfo]. + /// Map toJson() { return { 'id': id, diff --git a/lib/src/models/nearby_device_status.dart b/lib/src/models/nearby_device_status.dart index e594210..f0e49e9 100644 --- a/lib/src/models/nearby_device_status.dart +++ b/lib/src/models/nearby_device_status.dart @@ -1,3 +1,6 @@ +/// +/// Status of device connection. +/// enum NearbyDeviceStatus { available, connected, @@ -5,8 +8,36 @@ enum NearbyDeviceStatus { connecting, unavailable; + /// + /// Checks if status is [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) { if (code == null) { 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) { if (code == null) { return NearbyDeviceStatus.failed; diff --git a/lib/src/models/nearby_message.dart b/lib/src/models/nearby_message.dart index eb6f716..ed3d827 100644 --- a/lib/src/models/nearby_message.dart +++ b/lib/src/models/nearby_message.dart @@ -1,18 +1,54 @@ import 'package:nearby_service/nearby_service.dart'; -import 'package:nearby_service/src/utils/logger.dart'; +/// +/// Basic Message Abstraction. +/// abstract class NearbyMessage { + /// + /// The basic message contains only [value] - the content + /// to be sent or received. + /// const NearbyMessage({required this.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 { + /// + /// To send a message, in addition to [value], you need to pass [receiver] + /// to know to whom the message is addressed. + /// const OutgoingNearbyMessage({ required super.value, required this.receiver, }); + /// + /// Data of the user to whom the message is addressed. + /// + final NearbyDeviceInfo receiver; + + /// + /// Get [Map] from [OutgoingNearbyMessage]. + /// Map toJson() { return { '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 { + /// + /// The received message contains a [sender] in addition to [value], + /// to know from whom the message came. + /// const ReceivedNearbyMessage({ required super.value, required this.sender, }); + /// + /// Get [ReceivedNearbyMessage] from [Map]. + /// factory ReceivedNearbyMessage.fromJson(Map? json) { - try { - return ReceivedNearbyMessage( - value: json?['message'] ?? '', - sender: NearbyDeviceInfo.fromJson(json?['sender']), - ); - } catch (e) { - Logger.error(e); - throw NearbyServiceException('Can\'t map to device $json'); - } + return ReceivedNearbyMessage( + value: json?['message'] ?? '', + sender: NearbyDeviceInfo.fromJson(json?['sender']), + ); } + /// + /// Data of the user from whom the message came. + /// 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}'; + } } diff --git a/lib/src/platforms/android/models/nearby_connection_info.dart b/lib/src/platforms/android/models/nearby_connection_info.dart index 5f6bf9b..289f40d 100644 --- a/lib/src/platforms/android/models/nearby_connection_info.dart +++ b/lib/src/platforms/android/models/nearby_connection_info.dart @@ -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 { - static const unknown = 'unknown'; - + /// + /// The class representing the Android class + /// [WifiP2pInfo](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo). + /// const NearbyConnectionAndroidInfo({ required this.ownerIpAddress, required this.groupFormed, required this.isGroupOwner, }); - factory NearbyConnectionAndroidInfo.fromJson(Map json) { - final ownerIpAddress = (json['groupOwnerAddress'] ?? unknown) as String; + /// + /// Get [NearbyConnectionAndroidInfo] from [Map]. + /// + factory NearbyConnectionAndroidInfo.fromJson(Map? json) { + final ownerIpAddress = + (json?['groupOwnerAddress'] ?? kNearbyUnknown) as String; return NearbyConnectionAndroidInfo( ownerIpAddress: ownerIpAddress.replaceFirst('/', ''), - groupFormed: json['groupFormed'] ?? false, - isGroupOwner: json['isGroupOwner'] ?? false, + groupFormed: json?['groupFormed'] ?? false, + isGroupOwner: json?['isGroupOwner'] ?? false, ); } + /// + /// Group owner address. + /// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo) + /// 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; + + /// + /// 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; + + @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 { NearbyConnectionInfoMapper._(); + /// + /// Converts JSON to a [NearbyConnectionAndroidInfo]. + /// static NearbyConnectionAndroidInfo? mapToInfo(dynamic value) { - final jsonValue = jsonDecode(value) as Map?; - if (jsonValue == null) { + if (value == null) { return null; } - return NearbyConnectionAndroidInfo.fromJson(jsonValue); + final decoded = JSONDecoder.decodeMap(value); + return NearbyConnectionAndroidInfo.fromJson(decoded); } } diff --git a/lib/src/platforms/android/models/nearby_device.dart b/lib/src/platforms/android/models/nearby_device.dart index 5aa2ee9..72cdeed 100644 --- a/lib/src/platforms/android/models/nearby_device.dart +++ b/lib/src/platforms/android/models/nearby_device.dart @@ -1,7 +1,21 @@ 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 { + /// + /// 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({ required String deviceName, required super.status, @@ -20,13 +34,16 @@ class NearbyAndroidDevice extends NearbyDevice { ), ); + /// + /// Gets [NearbyAndroidDevice] from [Map] + /// factory NearbyAndroidDevice.fromJson(Map? json) { return NearbyAndroidDevice( - deviceName: json?['deviceName'] ?? NearbyDevice.unknown, - deviceAddress: json?['deviceAddress'] ?? NearbyDevice.unknown, + deviceName: json?['deviceName'] ?? kNearbyUnknown, + deviceAddress: json?['deviceAddress'] ?? kNearbyUnknown, isGroupOwner: json?['isGroupOwner'] ?? false, isServiceDiscoveryCapable: json?['isServiceDiscoveryCapable'] ?? false, - primaryDeviceType: json?['primaryDeviceType'] ?? NearbyDevice.unknown, + primaryDeviceType: json?['primaryDeviceType'] ?? kNearbyUnknown, secondaryDeviceType: json?['secondaryDeviceType'], wpsDisplaySupported: json?['wpsDisplaySupported'] ?? 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; + + /// + /// True if the device is a group owner. + /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice) + /// 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; + + /// + /// Primary device type identifies the type of device. + /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice) + /// 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; + + /// + /// True if WPS keypad configuration is supported. + /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice) + /// 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; + + /// + /// True if WPS display configuration is supported. + /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice) + /// 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 { @override List mapToDeviceList(dynamic value) { - final decoded = Decoder.decodeList(value); + final decoded = JSONDecoder.decodeList(value); return [ ...?decoded?.map( (e) => NearbyAndroidDevice.fromJson(e as Map?), @@ -58,6 +147,6 @@ class NearbyAndroidMapper implements NearbyDeviceMapper { @override NearbyDevice? mapToDevice(dynamic value) { - return NearbyAndroidDevice.fromJson(Decoder.decodeMap(value)); + return NearbyAndroidDevice.fromJson(JSONDecoder.decodeMap(value)); } } diff --git a/lib/src/platforms/android/nearby_android_service.dart b/lib/src/platforms/android/nearby_android_service.dart index 122e9ca..ff9b4d3 100644 --- a/lib/src/platforms/android/nearby_android_service.dart +++ b/lib/src/platforms/android/nearby_android_service.dart @@ -5,18 +5,30 @@ import 'package:nearby_service/nearby_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 { late final _socketService = NearbySocketService(this); @override - ValueListenable get isCommunicationChannelConnecting { - return _socketService.isConnecting; + ValueListenable get communicationChannelState { + return _socketService.state; } NearbyConnectionAndroidInfo? get 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 Future initialize({ NearbyInitializeData data = const NearbyInitializeData(), @@ -24,40 +36,48 @@ class NearbyAndroidService extends NearbyService { return NearbyServiceAndroidPlatform.instance.initialize(); } + /// + /// Starts discovery of the Wifi Direct network. + /// @override Future discover() { return NearbyServiceAndroidPlatform.instance.discover(); } + /// + /// Stops discovery of the Wifi Direct network. + /// @override Future stopDiscovery() { return NearbyServiceAndroidPlatform.instance.stopDiscovery(); } + /// + /// Connects to the [device] on the Wifi Direct network. + /// + /// Note! Requires [NearbyAndroidDevice] to be passed. + /// @override Future connect(NearbyDevice device) { _requireAndroidDevice(device); return NearbyServiceAndroidPlatform.instance.connect(device.info.id); } + /// + /// Disconnects from the [device] on the Wifi Direct network. + /// + /// Note! Requires [NearbyAndroidDevice] to be passed. + /// @override Future disconnect(NearbyDevice device) { _requireAndroidDevice(device); return NearbyServiceAndroidPlatform.instance.disconnect(device.info.id); } - Future requestPermissions() { - return NearbyServiceAndroidPlatform.instance.requestPermissions(); - } - - Future checkWifiService() { - return NearbyServiceAndroidPlatform.instance.checkWifiService(); - } - - Future getConnectionInfo() { - return NearbyServiceAndroidPlatform.instance.getConnectionInfo(); - } - + /// + /// Starts a socket service to transfer data. Uses device with + /// [NearbyCommunicationChannelData.connectedDeviceId]. + /// @override FutureOr startCommunicationChannel( NearbyCommunicationChannelData data, @@ -65,16 +85,51 @@ class NearbyAndroidService extends NearbyService { return _socketService.startSocket(data: data); } + /// + /// Ends the socket service to stop transferring data. + /// @override FutureOr endCommunicationChannel() { return _socketService.cancel(); } + /// + /// Adds [OutgoingNearbyMessage] to the socket. + /// @override FutureOr send(OutgoingNearbyMessage 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 requestPermissions() { + return NearbyServiceAndroidPlatform.instance.requestPermissions(); + } + + /// + /// Checks if Wi-fi is enabled. + /// **Wi-fi must be enabled for Android to use the plugin!** + /// + Future checkWifiService() { + return NearbyServiceAndroidPlatform.instance.checkWifiService(); + } + + /// + /// Returns [NearbyConnectionAndroidInfo] - + /// information about the connection information. + /// + Future getConnectionInfo() { + return NearbyServiceAndroidPlatform.instance.getConnectionInfo(); + } + void _requireAndroidDevice(NearbyDevice device) { assert( device is NearbyAndroidDevice, diff --git a/lib/src/platforms/android/socket_service/nearby_socket_service.dart b/lib/src/platforms/android/socket_service/nearby_socket_service.dart index 7f58207..3b53e4a 100644 --- a/lib/src/platforms/android/socket_service/nearby_socket_service.dart +++ b/lib/src/platforms/android/socket_service/nearby_socket_service.dart @@ -12,6 +12,9 @@ part 'ping_manager.dart'; part 'network.dart'; +/// +/// A service for creating a communication channel on the Android platform. +/// class NearbySocketService { NearbySocketService(this._manager); @@ -19,17 +22,27 @@ class NearbySocketService { final _pingManager = NearbySocketPingManager(); final _network = NearbyServiceNetwork(); - final isConnecting = ValueNotifier(false); + final state = ValueNotifier(CommunicationChannelState.notConnected); NearbyConnectionAndroidInfo? connectionInfo; String? _connectedDeviceId; WebSocket? _socket; StreamSubscription? _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 startSocket({ required NearbyCommunicationChannelData data, }) async { - isConnecting.value = true; + state.value = CommunicationChannelState.loading; _connectedDeviceId = data.connectedDeviceId; connectionInfo = await _manager.getConnectionInfo(); if (connectionInfo != null && connectionInfo!.groupFormed) { @@ -55,15 +68,18 @@ class NearbySocketService { return false; } + /// + /// Add [OutgoingNearbyMessage]'s JSON representation to [_socket]. + /// Future send(OutgoingNearbyMessage message) async { if (_socket != null && message.receiver.id == _connectedDeviceId) { - final sender = await _manager.getCurrentDevice(); + final sender = await _manager.getCurrentDeviceInfo(); if (sender != null) { _socket!.add( jsonEncode( { 'message': message.value, - 'sender': sender.info.toJson(), + 'sender': sender.toJson(), }, ), ); @@ -73,9 +89,11 @@ class NearbySocketService { return false; } + /// + /// Turns off [_messagesSubscription] and [_socket]. + /// Future cancel() async { try { - isConnecting.value = false; await _messagesSubscription?.cancel(); _messagesSubscription = null; _socket = null; @@ -153,7 +171,7 @@ class NearbySocketService { void _createSocketSubscription(NearbyServiceStreamListener socketListener) { Logger.debug('Starting socket subscription'); - isConnecting.value = false; + if (_connectedDeviceId != null) { _messagesSubscription = _socket ?.map(MessagesStreamMapper.toMessage) @@ -162,17 +180,24 @@ class NearbySocketService { .map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!)) .listen( socketListener.onData, - onDone: socketListener.onDone, + onDone: () { + state.value = CommunicationChannelState.notConnected; + socketListener.onDone?.call(); + }, onError: (e, s) { Logger.error(e); + state.value = CommunicationChannelState.notConnected; socketListener.onError?.call(e, s); }, cancelOnError: socketListener.cancelOnError, ); } if (_messagesSubscription != null) { + state.value = CommunicationChannelState.connected; Logger.info('Socket subscription was created successfully'); socketListener.onCreated?.call(_messagesSubscription!); + } else { + state.value = CommunicationChannelState.notConnected; } } } diff --git a/lib/src/platforms/ios/models/nearby_device.dart b/lib/src/platforms/ios/models/nearby_device.dart index 751fd91..001afdf 100644 --- a/lib/src/platforms/ios/models/nearby_device.dart +++ b/lib/src/platforms/ios/models/nearby_device.dart @@ -1,7 +1,18 @@ 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 { + /// + /// 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({ required super.info, required super.status, @@ -10,6 +21,9 @@ class NearbyIOSDevice extends NearbyDevice { this.deviceType, }); + /// + /// Gets [NearbyIOSDevice] from [Map]. + /// factory NearbyIOSDevice.fromJson(Map? json) { return NearbyIOSDevice( info: NearbyDeviceInfo.fromJson(json), @@ -20,15 +34,45 @@ class NearbyIOSDevice extends NearbyDevice { ); } + /// + /// `UIDevice.current.systemName` from IOS Platform. + /// final String? os; + + /// + /// `UIDevice.current.systemVersion` from IOS Platform. + /// final String? osVersion; + + /// + /// `UIDevice.current.model` from IOS Platform. + /// 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 { @override List mapToDeviceList(dynamic value) { - final decoded = Decoder.decodeList(value); + final decoded = JSONDecoder.decodeList(value); return [ ...?decoded?.map( (e) => NearbyIOSDevice.fromJson(e as Map?), @@ -39,7 +83,7 @@ class NearbyIOSMapper implements NearbyDeviceMapper { @override NearbyDevice? mapToDevice(dynamic value) { return NearbyIOSDevice.fromJson( - Decoder.decodeMap(value), + JSONDecoder.decodeMap(value), ); } } diff --git a/lib/src/platforms/ios/nearby_ios_service.dart b/lib/src/platforms/ios/nearby_ios_service.dart index 8cd4644..5ef825d 100644 --- a/lib/src/platforms/ios/nearby_ios_service.dart +++ b/lib/src/platforms/ios/nearby_ios_service.dart @@ -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/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 { final _isBrowser = ValueNotifier(true); - final _isCommunicationChannelConnecting = ValueNotifier(false); + final _state = ValueNotifier(CommunicationChannelState.notConnected); StreamSubscription? _messagesSubscription; @override - ValueListenable get isCommunicationChannelConnecting => - _isCommunicationChannelConnecting; + ValueListenable get communicationChannelState => + _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 get isBrowser => _isBrowser; String get _currentConnectionType { 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 Future initialize({ NearbyInitializeData data = const NearbyInitializeData(), @@ -37,6 +69,12 @@ class NearbyIOSService extends NearbyService { 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 Future discover() async { final result = _isBrowser.value @@ -50,6 +88,12 @@ class NearbyIOSService extends NearbyService { 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 Future stopDiscovery() async { final result = _isBrowser.value @@ -64,6 +108,14 @@ class NearbyIOSService extends NearbyService { 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 Future connect(NearbyDevice device) async { _requireIOSDevice(device); @@ -81,6 +133,11 @@ class NearbyIOSService extends NearbyService { return result; } + /// + /// Disconnects from the [device] on the P2P network. + /// + /// Note! Requires [NearbyIOSDevice] to be passed. + /// @override Future disconnect(NearbyDevice device) async { _requireIOSDevice(device); @@ -95,12 +152,16 @@ class NearbyIOSService extends NearbyService { return result; } + /// + /// Starts listening for messages from device with + /// [NearbyCommunicationChannelData.connectedDeviceId]. + /// @override FutureOr startCommunicationChannel( NearbyCommunicationChannelData data, ) async { Logger.debug('Creating messages subscription'); - _isCommunicationChannelConnecting.value = true; + _state.value = CommunicationChannelState.loading; await endCommunicationChannel(); final eventListener = data.eventListener; _messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream @@ -110,9 +171,13 @@ class NearbyIOSService extends NearbyService { .cast() .listen( eventListener.onData, - onDone: eventListener.onDone, + onDone: () { + _state.value = CommunicationChannelState.notConnected; + eventListener.onDone?.call(); + }, onError: (e, s) { Logger.error(e); + _state.value = CommunicationChannelState.notConnected; eventListener.onError?.call(e, s); }, cancelOnError: eventListener.cancelOnError, @@ -120,11 +185,18 @@ class NearbyIOSService extends NearbyService { if (_messagesSubscription != null) { Logger.info('Messages subscription was created successfully'); eventListener.onCreated?.call(_messagesSubscription!); + _state.value = CommunicationChannelState.connected; + } else { + _state.value = CommunicationChannelState.notConnected; } - _isCommunicationChannelConnecting.value = false; + return true; } + /// + /// Stops listening for messages from previously passed device with + /// [NearbyCommunicationChannelData.connectedDeviceId]. + /// @override FutureOr endCommunicationChannel() async { await _messagesSubscription?.cancel(); @@ -133,15 +205,30 @@ class NearbyIOSService extends NearbyService { return true; } + /// + /// Sends [OutgoingNearbyMessage] to [OutgoingNearbyMessage.receiver] via + /// IOS platform. + /// @override Future send(OutgoingNearbyMessage 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 getSavedDeviceName() { return NearbyServiceIOSPlatform.instance.getSavedDeviceName(); } + /// + /// Changes the [isBrowser] to the passed [value]. + /// void setIsBrowser({required bool value}) { Logger.debug('Is Browser Value was set to $value'); _isBrowser.value = value; diff --git a/lib/src/types/communication_channel_data.dart b/lib/src/types/communication_channel_data.dart index 1af136a..e76719e 100644 --- a/lib/src/types/communication_channel_data.dart +++ b/lib/src/types/communication_channel_data.dart @@ -3,26 +3,102 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:nearby_service/nearby_service.dart'; -class NearbyCommunicationChannelData { +/// +/// 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( this.connectedDeviceId, { required this.eventListener, this.androidData = const NearbyAndroidCommunicationChannelData(), }); + /// + /// Identifier of the device to be connected to. + /// final String connectedDeviceId; + + /// + /// Listener for message stream changes. + /// final NearbyServiceStreamListener eventListener; + + /// + /// Android-specific connection data. + /// 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 { + /// + /// 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({ this.clientReconnectInterval = const Duration(seconds: 5), this.serverListener, this.port = 4045, }); + /// + /// The interval at which the client will ping the server + /// while waiting for it to be created. + /// final Duration clientReconnectInterval; + + /// + /// Listener of events that come to the server. + /// final ValueChanged? serverListener; 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}'; + } } diff --git a/lib/src/types/initialize_data.dart b/lib/src/types/initialize_data.dart index 3f82e9a..fedd4bd 100644 --- a/lib/src/types/initialize_data.dart +++ b/lib/src/types/initialize_data.dart @@ -1,5 +1,17 @@ +/// +/// Data for plugin initialization. +/// 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}); + /// + /// The device name for IOS on the P2P network. + /// final String? iosDeviceName; } diff --git a/lib/src/types/nearby_service_stream_listener.dart b/lib/src/types/nearby_service_stream_listener.dart index 89c0c64..4ce1272 100644 --- a/lib/src/types/nearby_service_stream_listener.dart +++ b/lib/src/types/nearby_service_stream_listener.dart @@ -2,7 +2,14 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +/// +/// Stream Subscription Listener. +/// class NearbyServiceStreamListener { + /// + /// It is required to pass the [onData] parameter to process the + /// data that came through the stream. + /// const NearbyServiceStreamListener({ required this.onData, this.onCreated, diff --git a/lib/src/utils/exception.dart b/lib/src/utils/exception.dart index fee02f7..788ce6f 100644 --- a/lib/src/utils/exception.dart +++ b/lib/src/utils/exception.dart @@ -2,17 +2,28 @@ import 'dart:io'; import 'package:nearby_service/src/utils/logger.dart'; +/// +/// Nearby Service Plugin Exception. +/// +/// Indicates what problem occurred in the plugin operation. +/// class NearbyServiceException implements Exception { NearbyServiceException(this.error) { Logger.error(error); } + /// + /// A call from an unsupported platform. + /// factory NearbyServiceException.unsupportedPlatform({required String caller}) { return NearbyServiceException( '$caller is not supported for platform ${Platform.operatingSystem}', ); } + /// + /// A decoding error. + /// factory NearbyServiceException.unsupportedDecoding(dynamic value) { return NearbyServiceException( 'Got unknown value=$value with runtimeType=${value.runtimeType}', diff --git a/lib/src/utils/decoder.dart b/lib/src/utils/json_decoder.dart similarity index 94% rename from lib/src/utils/decoder.dart rename to lib/src/utils/json_decoder.dart index 5230833..3143892 100644 --- a/lib/src/utils/decoder.dart +++ b/lib/src/utils/json_decoder.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:nearby_service/nearby_service.dart'; -class Decoder { - Decoder._(); +class JSONDecoder { + JSONDecoder._(); static Map? decodeMap(dynamic value) { if (value == null) { diff --git a/lib/src/utils/log_level.dart b/lib/src/utils/log_level.dart index ea112db..e719557 100644 --- a/lib/src/utils/log_level.dart +++ b/lib/src/utils/log_level.dart @@ -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 { debug, info, diff --git a/lib/src/utils/stream_mapper.dart b/lib/src/utils/stream_mapper.dart index 4d437ec..51096a7 100644 --- a/lib/src/utils/stream_mapper.dart +++ b/lib/src/utils/stream_mapper.dart @@ -1,5 +1,5 @@ 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'; @@ -19,7 +19,7 @@ abstract class MessagesStreamMapper { static ReceivedNearbyMessage? toMessage(dynamic event) { try { - final decoded = Decoder.decodeMap(event); + final decoded = JSONDecoder.decodeMap(event); return ReceivedNearbyMessage.fromJson(decoded); } catch (e) { Logger.debug( diff --git a/lib/src/utils/unknown.dart b/lib/src/utils/unknown.dart new file mode 100644 index 0000000..aa77eea --- /dev/null +++ b/lib/src/utils/unknown.dart @@ -0,0 +1 @@ +const kNearbyUnknown = 'unknown'; diff --git a/test/nearby_service_test.dart b/test/nearby_service_test.dart index 60a0ae4..c089ba4 100644 --- a/test/nearby_service_test.dart +++ b/test/nearby_service_test.dart @@ -50,7 +50,7 @@ class MockNearbyServicePlatform } @override - Future getCurrentDevice() { + Future getCurrentDeviceInfo() { // TODO: implement getCurrentDevice throw UnimplementedError(); }