first commit

This commit is contained in:
ksenia312
2024-01-31 14:16:22 +01:00
commit 541fbeaaf3
132 changed files with 6463 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
export 'nearby_device.dart';
export 'nearby_device_status.dart';
export 'nearby_message.dart';
+69
View File
@@ -0,0 +1,69 @@
import 'dart:io';
import 'package:nearby_service/nearby_service.dart';
abstract class NearbyDevice {
static const unknown = 'unknown';
const NearbyDevice({required this.info, required this.status});
final NearbyDeviceInfo info;
final NearbyDeviceStatus status;
T? byPlatform<T>({
T Function(NearbyDevice)? onAny,
T Function(NearbyAndroidDevice)? onAndroid,
T Function(NearbyIOSDevice)? onIOS,
}) {
if (this is NearbyIOSDevice && onIOS != null) {
return onIOS(this as NearbyIOSDevice);
} else if (this is NearbyAndroidDevice && onAndroid != null) {
return onAndroid(this as NearbyAndroidDevice);
} else {
return onAny?.call(this);
}
}
}
abstract interface class NearbyDeviceMapper {
static NearbyDeviceMapper get instance {
if (Platform.isAndroid) {
return NearbyAndroidMapper();
}
if (Platform.isIOS) {
return NearbyIOSMapper();
}
throw NearbyServiceException.unsupportedPlatform(
caller: 'NearbyDeviceMapper',
);
}
List<NearbyDevice> mapToDeviceList(dynamic value);
NearbyDevice? mapToDevice(dynamic value);
}
class NearbyDeviceInfo {
const NearbyDeviceInfo({
required this.displayName,
required this.id,
});
factory NearbyDeviceInfo.fromJson(Map<String, dynamic>? json) {
return NearbyDeviceInfo(
displayName: json?['displayName'] ?? NearbyDevice.unknown,
id: json?['id'] ?? NearbyDevice.unknown,
);
}
final String displayName;
final String id;
Map<String, dynamic> toJson() {
return {
'id': id,
'displayName': displayName,
};
}
}
+36
View File
@@ -0,0 +1,36 @@
enum NearbyDeviceStatus {
available,
connected,
failed,
connecting,
unavailable;
bool get isConnected => this == NearbyDeviceStatus.connected;
static NearbyDeviceStatus fromAndroidCode(num? code) {
if (code == null) {
return NearbyDeviceStatus.failed;
}
return switch (code) {
(0) => NearbyDeviceStatus.connected,
(1) => NearbyDeviceStatus.connecting,
(2) => NearbyDeviceStatus.failed,
(3) => NearbyDeviceStatus.available,
(4) => NearbyDeviceStatus.unavailable,
(_) => NearbyDeviceStatus.failed,
};
}
static NearbyDeviceStatus fromIosCode(String? code) {
if (code == null) {
return NearbyDeviceStatus.failed;
}
final value = num.tryParse(code);
return switch (value) {
(0) => NearbyDeviceStatus.available,
(1) => NearbyDeviceStatus.connecting,
(2) => NearbyDeviceStatus.connected,
(_) => NearbyDeviceStatus.failed,
};
}
}
+45
View File
@@ -0,0 +1,45 @@
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart';
abstract class NearbyMessage {
const NearbyMessage({required this.value});
final String value;
}
class OutgoingNearbyMessage extends NearbyMessage {
const OutgoingNearbyMessage({
required super.value,
required this.receiver,
});
Map<String, dynamic> toJson() {
return {
'message': value,
'receiver': receiver.toJson(),
};
}
final NearbyDeviceInfo receiver;
}
class ReceivedNearbyMessage extends NearbyMessage {
const ReceivedNearbyMessage({
required super.value,
required this.sender,
});
factory ReceivedNearbyMessage.fromJson(Map<String, dynamic>? 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');
}
}
final NearbyDeviceInfo sender;
}
+4
View File
@@ -0,0 +1,4 @@
export 'nearby_android_service.dart';
export 'nearby_service_android_interface.dart';
export 'models/nearby_connection_info.dart';
export 'models/nearby_device.dart';
@@ -0,0 +1,36 @@
import 'dart:convert';
class NearbyConnectionAndroidInfo {
static const unknown = 'unknown';
const NearbyConnectionAndroidInfo({
required this.ownerIpAddress,
required this.groupFormed,
required this.isGroupOwner,
});
factory NearbyConnectionAndroidInfo.fromJson(Map<String, dynamic> json) {
final ownerIpAddress = (json['groupOwnerAddress'] ?? unknown) as String;
return NearbyConnectionAndroidInfo(
ownerIpAddress: ownerIpAddress.replaceFirst('/', ''),
groupFormed: json['groupFormed'] ?? false,
isGroupOwner: json['isGroupOwner'] ?? false,
);
}
final String ownerIpAddress;
final bool isGroupOwner;
final bool groupFormed;
}
class NearbyConnectionInfoMapper {
NearbyConnectionInfoMapper._();
static NearbyConnectionAndroidInfo? mapToInfo(dynamic value) {
final jsonValue = jsonDecode(value) as Map<String, dynamic>?;
if (jsonValue == null) {
return null;
}
return NearbyConnectionAndroidInfo.fromJson(jsonValue);
}
}
@@ -0,0 +1,63 @@
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/decoder.dart';
class NearbyAndroidDevice extends NearbyDevice {
NearbyAndroidDevice({
required String deviceName,
required super.status,
required this.deviceAddress,
required this.isGroupOwner,
required this.isServiceDiscoveryCapable,
required this.primaryDeviceType,
required this.wpsKeypadSupported,
required this.wpsPbcSupported,
required this.wpsDisplaySupported,
this.secondaryDeviceType,
}) : super(
info: NearbyDeviceInfo(
displayName: deviceName,
id: deviceAddress,
),
);
factory NearbyAndroidDevice.fromJson(Map<String, dynamic>? json) {
return NearbyAndroidDevice(
deviceName: json?['deviceName'] ?? NearbyDevice.unknown,
deviceAddress: json?['deviceAddress'] ?? NearbyDevice.unknown,
isGroupOwner: json?['isGroupOwner'] ?? false,
isServiceDiscoveryCapable: json?['isServiceDiscoveryCapable'] ?? false,
primaryDeviceType: json?['primaryDeviceType'] ?? NearbyDevice.unknown,
secondaryDeviceType: json?['secondaryDeviceType'],
wpsDisplaySupported: json?['wpsDisplaySupported'] ?? false,
wpsKeypadSupported: json?['wpsKeypadSupported'] ?? false,
wpsPbcSupported: json?['wpsPbcSupported'] ?? false,
status: NearbyDeviceStatus.fromAndroidCode(json?['status']),
);
}
final String deviceAddress;
final bool isGroupOwner;
final bool isServiceDiscoveryCapable;
final String primaryDeviceType;
final String? secondaryDeviceType;
final bool wpsKeypadSupported;
final bool wpsPbcSupported;
final bool wpsDisplaySupported;
}
class NearbyAndroidMapper implements NearbyDeviceMapper {
@override
List<NearbyDevice> mapToDeviceList(dynamic value) {
final decoded = Decoder.decodeList(value);
return [
...?decoded?.map(
(e) => NearbyAndroidDevice.fromJson(e as Map<String, dynamic>?),
),
];
}
@override
NearbyDevice? mapToDevice(dynamic value) {
return NearbyAndroidDevice.fromJson(Decoder.decodeMap(value));
}
}
@@ -0,0 +1,84 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
import 'socket_service/nearby_socket_service.dart';
class NearbyAndroidService extends NearbyService {
late final _socketService = NearbySocketService(this);
@override
ValueListenable<bool> get isCommunicationChannelConnecting {
return _socketService.isConnecting;
}
NearbyConnectionAndroidInfo? get connectionInfo {
return _socketService.connectionInfo;
}
@override
Future<bool> initialize({
NearbyInitializeData data = const NearbyInitializeData(),
}) {
return NearbyServiceAndroidPlatform.instance.initialize();
}
@override
Future<bool> discover() {
return NearbyServiceAndroidPlatform.instance.discover();
}
@override
Future<bool> stopDiscovery() {
return NearbyServiceAndroidPlatform.instance.stopDiscovery();
}
@override
Future<bool> connect(NearbyDevice device) {
_requireAndroidDevice(device);
return NearbyServiceAndroidPlatform.instance.connect(device.info.id);
}
@override
Future<bool> disconnect(NearbyDevice device) {
_requireAndroidDevice(device);
return NearbyServiceAndroidPlatform.instance.disconnect(device.info.id);
}
Future<bool> requestPermissions() {
return NearbyServiceAndroidPlatform.instance.requestPermissions();
}
Future<bool> checkWifiService() {
return NearbyServiceAndroidPlatform.instance.checkWifiService();
}
Future<NearbyConnectionAndroidInfo?> getConnectionInfo() {
return NearbyServiceAndroidPlatform.instance.getConnectionInfo();
}
@override
FutureOr<bool> startCommunicationChannel(
NearbyCommunicationChannelData data,
) {
return _socketService.startSocket(data: data);
}
@override
FutureOr<bool> endCommunicationChannel() {
return _socketService.cancel();
}
@override
FutureOr<bool> send(OutgoingNearbyMessage message) {
return _socketService.send(message);
}
void _requireAndroidDevice(NearbyDevice device) {
assert(
device is NearbyAndroidDevice,
'The Nearby Android Service can only work with the NearbyAndroidDevice and not with ${device.runtimeType}',
);
}
}
@@ -0,0 +1,57 @@
import 'package:nearby_service/src/platforms/android/models/nearby_connection_info.dart';
import 'package:nearby_service/src/platforms/android/nearby_service_android_method_channel.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
abstract class NearbyServiceAndroidPlatform extends PlatformInterface {
NearbyServiceAndroidPlatform() : super(token: _token);
static final Object _token = Object();
static NearbyServiceAndroidPlatform _instance =
MethodChannelAndroidNearbyService();
/// The default instance of [NearbyServiceAndroidPlatform] to use.
///
/// Defaults to [NearbyServiceAndroidPlatform].
static NearbyServiceAndroidPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [NearbyServiceAndroidPlatform] when
/// they register themselves.
static set instance(NearbyServiceAndroidPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<bool> initialize() {
throw UnimplementedError('initialize() has not been implemented.');
}
Future<bool> requestPermissions() {
throw UnimplementedError('requestPermissions() has not been implemented.');
}
Future<bool> checkWifiService() {
throw UnimplementedError('checkWifiService() has not been implemented.');
}
Future<NearbyConnectionAndroidInfo?> getConnectionInfo() {
throw UnimplementedError('getConnectionInfo() has not been implemented.');
}
Future<bool> discover() {
throw UnimplementedError('discover() has not been implemented.');
}
Future<bool> stopDiscovery() {
throw UnimplementedError('stopDiscovery() has not been implemented.');
}
Future<bool> connect(String deviceAddress) {
throw UnimplementedError('connect() has not been implemented.');
}
Future<bool> disconnect(String deviceAddress) {
throw UnimplementedError('disconnect() has not been implemented.');
}
}
@@ -0,0 +1,67 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart';
/// An implementation of [NearbyServiceAndroidPlatform] that uses method channels.
class MethodChannelAndroidNearbyService extends NearbyServiceAndroidPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('nearby_service');
@override
Future<bool> initialize() async {
return (await methodChannel.invokeMethod<bool>(
'initialize',
{"logLevel": Logger.level.name},
)) ??
false;
}
@override
Future<bool> requestPermissions() async {
return (await methodChannel.invokeMethod<bool>('requestPermissions')) ??
false;
}
@override
Future<bool> checkWifiService() async {
return (await methodChannel.invokeMethod<bool>('checkWifiService')) ??
false;
}
@override
Future<NearbyConnectionAndroidInfo?> getConnectionInfo() async {
return NearbyConnectionInfoMapper.mapToInfo(
await methodChannel.invokeMethod('getConnectionInfo'),
);
}
@override
Future<bool> discover() async {
return (await methodChannel.invokeMethod<bool>('discover')) ?? false;
}
@override
Future<bool> stopDiscovery() async {
return (await methodChannel.invokeMethod<bool>('stopDiscovery')) ?? false;
}
@override
Future<bool> connect(String deviceAddress) async {
return (await methodChannel.invokeMethod<bool?>(
"connect",
{"deviceAddress": deviceAddress},
)) ??
false;
}
@override
Future<bool> disconnect(String deviceAddress) async {
return (await methodChannel.invokeMethod<bool?>(
"disconnect",
{"deviceAddress": deviceAddress},
)) ??
false;
}
}
@@ -0,0 +1,178 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart';
import 'package:nearby_service/src/utils/stream_mapper.dart';
part 'ping_manager.dart';
part 'network.dart';
class NearbySocketService {
NearbySocketService(this._manager);
final NearbyAndroidService _manager;
final _pingManager = NearbySocketPingManager();
final _network = NearbyServiceNetwork();
final isConnecting = ValueNotifier(false);
NearbyConnectionAndroidInfo? connectionInfo;
String? _connectedDeviceId;
WebSocket? _socket;
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
Future<bool> startSocket({
required NearbyCommunicationChannelData data,
}) async {
isConnecting.value = true;
_connectedDeviceId = data.connectedDeviceId;
connectionInfo = await _manager.getConnectionInfo();
if (connectionInfo != null && connectionInfo!.groupFormed) {
final androidData = data.androidData;
if (connectionInfo!.isGroupOwner) {
await _startServerSubscription(
serverListener: androidData.serverListener,
socketListener: data.eventListener,
info: connectionInfo!,
port: androidData.port,
);
return true;
} else {
await _tryConnectClient(
socketListener: data.eventListener,
reconnectInterval: androidData.clientReconnectInterval,
info: connectionInfo!,
port: androidData.port,
);
return true;
}
}
return false;
}
Future<bool> send(OutgoingNearbyMessage message) async {
if (_socket != null && message.receiver.id == _connectedDeviceId) {
final sender = await _manager.getCurrentDevice();
if (sender != null) {
_socket!.add(
jsonEncode(
{
'message': message.value,
'sender': sender.info.toJson(),
},
),
);
}
return true;
}
return false;
}
Future<bool> cancel() async {
try {
isConnecting.value = false;
await _messagesSubscription?.cancel();
_messagesSubscription = null;
_socket = null;
_connectedDeviceId = null;
return true;
} catch (e) {
return false;
}
}
Future<void> _tryConnectClient({
required NearbyServiceStreamListener socketListener,
required NearbyConnectionAndroidInfo info,
required int port,
required Duration reconnectInterval,
}) async {
final response = await _network.pingServer(
address: info.ownerIpAddress,
port: port,
);
if (await _pingManager.checkPong(response)) {
_socket = await _network.connectToSocket(
ownerIpAddress: info.ownerIpAddress,
port: port,
);
_createSocketSubscription(socketListener);
} else {
Logger.debug(
'Retry to connect to the server in ${reconnectInterval.inSeconds}s',
);
Future.delayed(reconnectInterval, () {
_tryConnectClient(
socketListener: socketListener,
reconnectInterval: reconnectInterval,
info: info,
port: port,
);
});
}
}
Future<void> _startServerSubscription({
required NearbyServiceStreamListener socketListener,
required NearbyConnectionAndroidInfo info,
required int port,
ValueChanged<HttpRequest>? serverListener,
}) async {
final httpServer = await _network.startServer(
ownerIpAddress: info.ownerIpAddress,
port: port,
);
httpServer?.listen(
(request) async {
serverListener?.call(request);
final isPing = await _pingManager.checkPing(request);
if (isPing) {
Logger.debug('Server got ping request');
_network.pongClient(request);
return;
}
if (request.uri.path == '/ws') {
_socket = await WebSocketTransformer.upgrade(request);
_createSocketSubscription(socketListener);
} else {
request.response
..statusCode = HttpStatus.notFound
..close();
Logger.error('Got unknown request ${request.requestedUri}');
}
},
);
}
void _createSocketSubscription(NearbyServiceStreamListener socketListener) {
Logger.debug('Starting socket subscription');
isConnecting.value = false;
if (_connectedDeviceId != null) {
_messagesSubscription = _socket
?.map(MessagesStreamMapper.toMessage)
.where((event) => event != null)
.cast<ReceivedNearbyMessage>()
.map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!))
.listen(
socketListener.onData,
onDone: socketListener.onDone,
onError: (e, s) {
Logger.error(e);
socketListener.onError?.call(e, s);
},
cancelOnError: socketListener.cancelOnError,
);
}
if (_messagesSubscription != null) {
Logger.info('Socket subscription was created successfully');
socketListener.onCreated?.call(_messagesSubscription!);
}
}
}
@@ -0,0 +1,71 @@
part of 'nearby_socket_service.dart';
class _Protocols {
static const http = 'http://';
static const ws = 'ws://';
}
class NearbyServiceNetwork {
final _httpClient = HttpClient();
final _random = Random();
Future<HttpClientResponse?> pingServer({
required String address,
required int port,
}) async {
try {
final url = Uri.parse('${_Protocols.http}$address:$port/');
final request = (await _httpClient.postUrl(url))
..write(_Commands.ping.name);
final response = await request.close();
return response;
} catch (e) {
Logger.error('Server is unreachable');
return null;
}
}
void pongClient(HttpRequest request) {
request.response
..write(_Commands.pong.name)
..close();
Logger.debug('Sent pong to client');
}
Future<HttpServer?> startServer({
required String ownerIpAddress,
required int port,
}) async {
try {
final url = 'ws://$ownerIpAddress:$port';
Logger.debug('Starting server on $url');
var server = await HttpServer.bind(
ownerIpAddress,
port,
shared: true,
);
Logger.info('Server running on $url');
return server;
} catch (e) {
Logger.error('Error starting socket: $e');
return null;
}
}
Future<WebSocket?> connectToSocket({
required String ownerIpAddress,
required int port,
}) async {
try {
final connectionId = _random.nextInt(1000) + 100;
final url = '${_Protocols.ws}$ownerIpAddress:$port/ws?as=$connectionId';
Logger.debug('Connecting to $url');
final socket = await WebSocket.connect(url);
Logger.info('Connected to $url');
return socket;
} catch (e) {
Logger.error('Error connecting to server: $e');
return null;
}
}
}
@@ -0,0 +1,25 @@
part of 'nearby_socket_service.dart';
enum _Commands { ping, pong }
class NearbySocketPingManager {
Future<bool> checkPing(HttpRequest request) async {
final body = await _getBody(request);
return body == _Commands.ping.name;
}
Future<bool> checkPong(HttpClientResponse? response) async {
if (response == null) return false;
final body = await _getBody(response);
return body == _Commands.pong.name;
}
static Future<String?> _getBody(Stream<List<int>> data) {
try {
return utf8.decoder.bind(data).join();
} catch (e) {
return Future.value(null);
}
}
}
+3
View File
@@ -0,0 +1,3 @@
export 'models/nearby_device.dart';
export 'nearby_ios_service.dart';
export 'nearby_service_ios_interface.dart';
@@ -0,0 +1,45 @@
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/decoder.dart';
class NearbyIOSDevice extends NearbyDevice {
NearbyIOSDevice({
required super.info,
required super.status,
this.os,
this.osVersion,
this.deviceType,
});
factory NearbyIOSDevice.fromJson(Map<String, dynamic>? json) {
return NearbyIOSDevice(
info: NearbyDeviceInfo.fromJson(json),
deviceType: json?["deviceType"],
os: json?["os"],
osVersion: json?["osVersion"],
status: NearbyDeviceStatus.fromIosCode(json?['state']),
);
}
final String? os;
final String? osVersion;
final String? deviceType;
}
class NearbyIOSMapper implements NearbyDeviceMapper {
@override
List<NearbyDevice> mapToDeviceList(dynamic value) {
final decoded = Decoder.decodeList(value);
return [
...?decoded?.map(
(e) => NearbyIOSDevice.fromJson(e as Map<String, dynamic>?),
),
];
}
@override
NearbyDevice? mapToDevice(dynamic value) {
return NearbyIOSDevice.fromJson(
Decoder.decodeMap(value),
);
}
}
@@ -0,0 +1,168 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart';
import 'package:nearby_service/src/utils/stream_mapper.dart';
class NearbyIOSService extends NearbyService {
final _isBrowser = ValueNotifier<bool>(true);
final _isCommunicationChannelConnecting = ValueNotifier<bool>(false);
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
@override
ValueListenable<bool> get isCommunicationChannelConnecting =>
_isCommunicationChannelConnecting;
ValueListenable<bool> get isBrowser => _isBrowser;
String get _currentConnectionType {
return _isBrowser.value ? 'browsing' : 'advertising';
}
@override
Future<bool> initialize({
NearbyInitializeData data = const NearbyInitializeData(),
}) async {
final result = await NearbyServiceIOSPlatform.instance.initialize(
data.iosDeviceName,
);
_logResult(
result,
onSuccess: 'Initialized ${data.iosDeviceName}',
onError: 'Failed to initialize ${data.iosDeviceName}',
);
return result;
}
@override
Future<bool> discover() async {
final result = _isBrowser.value
? await NearbyServiceIOSPlatform.instance.startBrowsing()
: await NearbyServiceIOSPlatform.instance.startAdvertising();
_logResult(
result,
onSuccess: 'Started $_currentConnectionType',
onError: 'Failed to start $_currentConnectionType',
);
return result;
}
@override
Future<bool> stopDiscovery() async {
final result = _isBrowser.value
? await NearbyServiceIOSPlatform.instance.stopBrowsing()
: await NearbyServiceIOSPlatform.instance.stopAdvertising();
_logResult(
result,
onSuccess: 'Stopped $_currentConnectionType',
onError: 'Failed to stop $_currentConnectionType',
);
return result;
}
@override
Future<bool> connect(NearbyDevice device) async {
_requireIOSDevice(device);
final result = _isBrowser.value
? await NearbyServiceIOSPlatform.instance.invite(device.info.id)
: await NearbyServiceIOSPlatform.instance.acceptInvite(device.info.id);
_logResult(
result,
onSuccess:
'${_isBrowser.value ? 'Sent invitation to' : 'Accepted invitation from'} '
'${device.info.id}',
onError: 'Failed to connect to ${device.info.id}',
);
return result;
}
@override
Future<bool> disconnect(NearbyDevice device) async {
_requireIOSDevice(device);
final result = await NearbyServiceIOSPlatform.instance.disconnect(
device.info.id,
);
_logResult(
result,
onSuccess: 'Disconnected from ${device.info.id}',
onError: 'Failed to disconnect from ${device.info.id}',
);
return result;
}
@override
FutureOr<bool> startCommunicationChannel(
NearbyCommunicationChannelData data,
) async {
Logger.debug('Creating messages subscription');
_isCommunicationChannelConnecting.value = true;
await endCommunicationChannel();
final eventListener = data.eventListener;
_messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream
.map(MessagesStreamMapper.toMessage)
.where((event) => event?.sender.id == data.connectedDeviceId)
.where((event) => event != null)
.cast<ReceivedNearbyMessage>()
.listen(
eventListener.onData,
onDone: eventListener.onDone,
onError: (e, s) {
Logger.error(e);
eventListener.onError?.call(e, s);
},
cancelOnError: eventListener.cancelOnError,
);
if (_messagesSubscription != null) {
Logger.info('Messages subscription was created successfully');
eventListener.onCreated?.call(_messagesSubscription!);
}
_isCommunicationChannelConnecting.value = false;
return true;
}
@override
FutureOr<bool> endCommunicationChannel() async {
await _messagesSubscription?.cancel();
_messagesSubscription = null;
Logger.debug('Communication channel was cancelled');
return true;
}
@override
Future<bool> send(OutgoingNearbyMessage message) {
return NearbyServiceIOSPlatform.instance.send(message);
}
Future<String?> getSavedDeviceName() {
return NearbyServiceIOSPlatform.instance.getSavedDeviceName();
}
void setIsBrowser({required bool value}) {
Logger.debug('Is Browser Value was set to $value');
_isBrowser.value = value;
}
void _logResult(
bool value, {
required String onSuccess,
required String onError,
}) {
if (value) {
Logger.info(onSuccess);
} else {
Logger.error(onError);
}
}
void _requireIOSDevice(NearbyDevice device) {
assert(
device is NearbyIOSDevice,
'The Nearby IOS Service can only work with the NearbyIOSDevice and not with ${device.runtimeType}',
);
}
}
@@ -0,0 +1,69 @@
import 'dart:async';
import 'package:nearby_service/nearby_service.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'nearby_service_ios_method_channel.dart';
abstract class NearbyServiceIOSPlatform extends PlatformInterface {
NearbyServiceIOSPlatform() : super(token: _token);
static final Object _token = Object();
static NearbyServiceIOSPlatform _instance = MethodChannelIOSNearbyService();
/// The default instance of [NearbyServiceIOSPlatform] to use.
static NearbyServiceIOSPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [NearbyServiceIOSPlatform] when
/// they register themselves.
static set instance(NearbyServiceIOSPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Stream get messagesStream {
throw UnimplementedError('messagesStream() has not been implemented.');
}
Future<bool> initialize([String? deviceName]) {
throw UnimplementedError('initialize() has not been implemented.');
}
Future<String?> getSavedDeviceName() {
throw UnimplementedError('getSavedDeviceName() has not been implemented.');
}
Future<bool> startBrowsing() {
throw UnimplementedError('startBrowsing() has not been implemented.');
}
Future<bool> startAdvertising() {
throw UnimplementedError('startAdvertising() has not been implemented.');
}
Future<bool> stopBrowsing() {
throw UnimplementedError('stopBrowsing() has not been implemented.');
}
Future<bool> stopAdvertising() {
throw UnimplementedError('stopAdvertising() has not been implemented.');
}
Future<bool> invite(String deviceId) {
throw UnimplementedError('invite() has not been implemented.');
}
Future<bool> acceptInvite(String deviceId) {
throw UnimplementedError('acceptInvite() has not been implemented.');
}
Future<bool> disconnect(String deviceId) {
throw UnimplementedError('disconnect() has not been implemented.');
}
Future<bool> send(OutgoingNearbyMessage message) {
throw UnimplementedError('send() has not been implemented.');
}
}
@@ -0,0 +1,95 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:nearby_service/nearby_service.dart';
/// An implementation of [NearbyServiceIOSPlatform] that uses method channels.
class MethodChannelIOSNearbyService extends NearbyServiceIOSPlatform {
final messageReceiver = StreamController.broadcast();
@override
Stream get messagesStream => messageReceiver.stream;
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('nearby_service');
@override
Future<bool> initialize([String? deviceName]) async {
methodChannel.setMethodCallHandler((handler) async {
switch (handler.method) {
case 'invoke_nearby_service_message_received':
messageReceiver.add(handler.arguments);
break;
}
});
return (await methodChannel.invokeMethod<bool>(
'initialize',
deviceName != null ? {"deviceName": deviceName} : null,
) ??
false);
}
@override
Future<String?> getSavedDeviceName() async {
return (await methodChannel.invokeMethod<String?>('getSavedDeviceName'));
}
@override
Future<bool> startAdvertising() async {
return (await methodChannel.invokeMethod<bool>('startAdvertising')) ??
false;
}
@override
Future<bool> startBrowsing() async {
return (await methodChannel.invokeMethod<bool>('startBrowsing')) ?? false;
}
@override
Future<bool> stopAdvertising() async {
return (await methodChannel.invokeMethod<bool>('stopAdvertising')) ?? false;
}
@override
Future<bool> stopBrowsing() async {
return (await methodChannel.invokeMethod<bool>('stopBrowsing')) ?? false;
}
@override
Future<bool> invite(String deviceId) async {
return (await methodChannel.invokeMethod<bool?>(
"invite",
{"deviceId": deviceId},
)) ??
false;
}
@override
Future<bool> acceptInvite(String deviceId) async {
return (await methodChannel.invokeMethod<bool?>(
"acceptInvite",
{"deviceId": deviceId},
)) ??
false;
}
@override
Future<bool> disconnect(String deviceId) async {
return (await methodChannel.invokeMethod<bool?>(
"disconnect",
{"deviceId": deviceId},
)) ??
false;
}
@override
Future<bool> send(OutgoingNearbyMessage message) async {
return (await methodChannel.invokeMethod<bool?>(
"send",
message.toJson(),
)) ??
false;
}
}
@@ -0,0 +1,28 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
class NearbyCommunicationChannelData<T> {
const NearbyCommunicationChannelData(
this.connectedDeviceId, {
required this.eventListener,
this.androidData = const NearbyAndroidCommunicationChannelData(),
});
final String connectedDeviceId;
final NearbyServiceStreamListener<ReceivedNearbyMessage> eventListener;
final NearbyAndroidCommunicationChannelData androidData;
}
class NearbyAndroidCommunicationChannelData {
const NearbyAndroidCommunicationChannelData({
this.clientReconnectInterval = const Duration(seconds: 5),
this.serverListener,
this.port = 4045,
});
final Duration clientReconnectInterval;
final ValueChanged<HttpRequest>? serverListener;
final int port;
}
+5
View File
@@ -0,0 +1,5 @@
class NearbyInitializeData {
const NearbyInitializeData({this.iosDeviceName});
final String? iosDeviceName;
}
@@ -0,0 +1,19 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
class NearbyServiceStreamListener<T> {
const NearbyServiceStreamListener({
required this.onData,
this.onCreated,
this.onDone,
this.onError,
this.cancelOnError,
});
final ValueChanged<T> onData;
final ValueChanged<StreamSubscription<T>>? onCreated;
final VoidCallback? onDone;
final void Function(Object, [StackTrace])? onError;
final bool? cancelOnError;
}
+3
View File
@@ -0,0 +1,3 @@
export 'communication_channel_data.dart';
export 'nearby_service_stream_listener.dart';
export 'initialize_data.dart';
+33
View File
@@ -0,0 +1,33 @@
import 'dart:convert';
import 'package:nearby_service/nearby_service.dart';
class Decoder {
Decoder._();
static Map<String, dynamic>? decodeMap(dynamic value) {
if (value == null) {
return null;
}
if (value is String) {
return jsonDecode(value) as Map<String, dynamic>;
}
if (value is Map<String, dynamic>) {
return value;
}
throw NearbyServiceException.unsupportedDecoding(value);
}
static List? decodeList(dynamic value) {
if (value == null) {
return null;
}
if (value is String) {
return jsonDecode(value) as List?;
}
if (value is List) {
return value;
}
throw NearbyServiceException.unsupportedDecoding(value);
}
}
+23
View File
@@ -0,0 +1,23 @@
import 'dart:io';
import 'package:nearby_service/src/utils/logger.dart';
class NearbyServiceException implements Exception {
NearbyServiceException(this.error) {
Logger.error(error);
}
factory NearbyServiceException.unsupportedPlatform({required String caller}) {
return NearbyServiceException(
'$caller is not supported for platform ${Platform.operatingSystem}',
);
}
factory NearbyServiceException.unsupportedDecoding(dynamic value) {
return NearbyServiceException(
'Got unknown value=$value with runtimeType=${value.runtimeType}',
);
}
final Object? error;
}
+6
View File
@@ -0,0 +1,6 @@
enum NearbyServiceLogLevel {
debug,
info,
error,
disabled,
}
+81
View File
@@ -0,0 +1,81 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
class Logger {
Logger._();
static NearbyServiceLogLevel level =
kDebugMode ? NearbyServiceLogLevel.debug : NearbyServiceLogLevel.error;
static void debug(String message) {
if (level.index <= NearbyServiceLogLevel.debug.index) {
debugPrint(
_messageWrapper(
message,
androidColor: AndroidConsoleColor.grey,
iosIcon: IOSConsoleIcon.settings,
),
);
}
}
static void info(String message) {
if (level.index <= NearbyServiceLogLevel.info.index) {
debugPrint(
_messageWrapper(
message,
androidColor: AndroidConsoleColor.green,
iosIcon: IOSConsoleIcon.success,
),
);
}
}
static void error(Object? error) {
if (level.index <= NearbyServiceLogLevel.error.index) {
debugPrint(
_messageWrapper(
'$error',
androidColor: AndroidConsoleColor.red,
iosIcon: IOSConsoleIcon.error,
),
);
}
}
static String _messageWrapper(
String message, {
required AndroidConsoleColor androidColor,
required IOSConsoleIcon iosIcon,
}) {
if (Platform.isAndroid) {
return '\x1B[${androidColor.value}m[NearbyService]: $message\x1B[0m';
}
if (Platform.isIOS) {
return '[NearbyService ${iosIcon.value}]: $message ';
}
return message;
}
}
enum AndroidConsoleColor {
grey(37),
green(32),
red(31);
const AndroidConsoleColor(this.value);
final int value;
}
enum IOSConsoleIcon {
settings('🛠'),
success(''),
error('');
const IOSConsoleIcon(this.value);
final String value;
}
+31
View File
@@ -0,0 +1,31 @@
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/decoder.dart';
import 'logger.dart';
abstract class MessagesStreamMapper {
static ReceivedNearbyMessage replaceId(
ReceivedNearbyMessage message,
String id,
) {
return ReceivedNearbyMessage(
value: message.value,
sender: NearbyDeviceInfo(
id: id,
displayName: message.sender.displayName,
),
);
}
static ReceivedNearbyMessage? toMessage(dynamic event) {
try {
final decoded = Decoder.decodeMap(event);
return ReceivedNearbyMessage.fromJson(decoded);
} catch (e) {
Logger.debug(
'Can\'t convert $event to ReceivedNearbyMessage',
);
}
return null;
}
}
+2
View File
@@ -0,0 +1,2 @@
export 'exception.dart';
export 'log_level.dart';