diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index d2391e7..c29e0a9 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -27,7 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
namespace "com.xenikii.nearby_service_example"
- compileSdk flutter.compileSdkVersion
+ compileSdk 34
ndkVersion flutter.ndkVersion
compileOptions {
diff --git a/example/android/build.gradle b/example/android/build.gradle
index ce647a4..ab1fdb4 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,5 +1,5 @@
buildscript {
- ext.kotlin_version = '1.7.10'
+ ext.kotlin_version = '1.9.22'
repositories {
google()
mavenCentral()
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 4760cc1..d5d971a 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -26,6 +26,10 @@
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
+ LSSupportsOpeningDocumentsInPlace
+
+ UIFileSharingEnabled
+
UIApplicationSupportsIndirectInputEvents
UILaunchStoryboardName
diff --git a/example/lib/components/action_dialog.dart b/example/lib/components/action_dialog.dart
new file mode 100644
index 0000000..1483b12
--- /dev/null
+++ b/example/lib/components/action_dialog.dart
@@ -0,0 +1,31 @@
+part of '../main.dart';
+
+class ActionDialog {
+ ActionDialog._();
+
+ static Future show(
+ BuildContext context, {
+ required String title,
+ required String subtitle,
+ }) {
+ return showDialog(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ title: Text(title),
+ content: Text(subtitle),
+ actions: [
+ ElevatedButton(
+ onPressed: () => Navigator.of(context).pop(true),
+ child: const Text('Yes'),
+ ),
+ ElevatedButton(
+ onPressed: () => Navigator.of(context).pop(false),
+ child: const Text('No'),
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 70d598e..8f97c3b 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,5 +1,6 @@
import 'dart:io';
+import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';
@@ -11,6 +12,8 @@ import 'components/app_snack_bar.dart';
part 'components/action_button.dart';
+part 'components/action_dialog.dart';
+
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
final service = AppService();
@@ -197,6 +200,8 @@ class AppService extends ChangeNotifier {
StreamSubscription? peersSubscription;
StreamSubscription? connectedDeviceSubscription;
+ final _filesAccepts = >{};
+
@override
void dispose() {
stopListeningAll();
@@ -373,14 +378,27 @@ class AppService extends ChangeNotifier {
Future startCommunicationChannel({
ValueChanged? listener,
+ ValueChanged? onFileSaved,
}) async {
final eventListener = NearbyServiceStreamListener(
- onCreated: (_) {
+ onCreated: () {
updateState(AppState.communicationChannelCreated);
},
- onData: (event) {
+ onMessage: (event) {
listener?.call(event);
},
+ onFile: (event) async {
+ final content = event.content;
+ final fileAcceptFuture = _filesAccepts[content.id];
+
+ if (fileAcceptFuture != null && (await fileAcceptFuture == true)) {
+ final downloadsDir = Directory('storage/emulated/0/Download');
+ final newFile = await event.file.copy(
+ '${downloadsDir.path}/${content.id}_${content.fileName}',
+ );
+ onFileSaved?.call(newFile);
+ }
+ },
onError: (e, [StackTrace? s]) {
stopListeningAll();
},
@@ -394,16 +412,30 @@ class AppService extends ChangeNotifier {
);
}
- void send(String message) {
+ void sendMessage(String message) {
if (connectedDevice == null) return;
_nearbyService.send(
OutgoingNearbyMessage(
- value: message,
+ content: NearbyMessageTextContent(value: message),
receiver: connectedDevice!.info,
),
);
}
+ void sendFile(String filePath) {
+ if (connectedDevice == null) return;
+ _nearbyService.send(
+ OutgoingNearbyMessage(
+ content: NearbyMessageFileContent(filePath: filePath),
+ receiver: connectedDevice!.info,
+ ),
+ );
+ }
+
+ void setFileAcceptFuture(String id, Future future) {
+ _filesAccepts[id] = future;
+ }
+
Future disconnect(NearbyDevice device) async {
try {
await _nearbyService.disconnect(device);
@@ -740,15 +772,11 @@ class _ConnectedBody extends StatelessWidget {
if (service.communicationChannelState !=
CommunicationChannelState.loading)
_ActionButton(
- onTap: () => service.startCommunicationChannel(
- listener: (event) => AppShackBar.show(
- Scaffold.of(context).context,
- event.value,
- subtitle: 'From ${event.sender.displayName} '
- '(ID: ${event.sender.id})',
- ),
- ),
title: 'Start communicate',
+ onTap: () => service.startCommunicationChannel(
+ listener: (event) => _listener(context, event),
+ onFileSaved: (file) => _onFileSaved(context, file),
+ ),
)
else
Text(
@@ -762,6 +790,37 @@ class _ConnectedBody extends StatelessWidget {
},
);
}
+
+ void _listener(BuildContext context, ReceivedNearbyMessage message) {
+ final senderSubtitle = 'From ${message.sender.displayName} '
+ '(ID: ${message.sender.id})';
+ message.content.get(
+ onText: (content) {
+ AppShackBar.show(
+ Scaffold.of(context).context,
+ content.value,
+ subtitle: senderSubtitle,
+ );
+ },
+ onFile: (content) {
+ context.read().setFileAcceptFuture(
+ content.id,
+ ActionDialog.show(
+ context,
+ title: 'File request ${content.fileName}',
+ subtitle: senderSubtitle,
+ ),
+ );
+ },
+ );
+ }
+
+ void _onFileSaved(BuildContext context, File file) {
+ AppShackBar.show(
+ Scaffold.of(context).context,
+ 'File saved to ${file.path}',
+ );
+ }
}
class _ConnectedSocketBody extends StatefulWidget {
@@ -773,6 +832,7 @@ class _ConnectedSocketBody extends StatefulWidget {
class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
String message = '';
+ String filePath = '';
@override
Widget build(BuildContext context) {
@@ -797,6 +857,7 @@ class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
+ flex: 2,
child: TextField(
onChanged: (value) => setState(() {
message = value;
@@ -810,15 +871,51 @@ class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
),
),
const SizedBox(width: 10),
- _ActionButton(
- title: 'Send',
- onTap: () {
- service.send(message);
- },
+ Flexible(
+ child: _ActionButton(
+ title: 'Send',
+ onTap: () {
+ service.sendMessage(message);
+ },
+ ),
),
],
),
),
+ const SizedBox(height: 10),
+ Flexible(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ flex: 2,
+ child: _ActionButton(
+ type: _ActionButtonType.warning,
+ title: 'Choose a file',
+ onTap: () async {
+ final result = await FilePicker.platform.pickFiles();
+ if (result != null && result.isSinglePick) {
+ setState(() {
+ filePath = result.paths.first!;
+ });
+ }
+ },
+ ),
+ ),
+ const SizedBox(width: 10),
+ Flexible(
+ child: _ActionButton(
+ title: 'Send',
+ onTap: () {
+ service.sendFile(filePath);
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 10),
+ Text('Selected file: $filePath'),
],
);
},
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index c05d17f..f5cec58 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -5,16 +5,17 @@ description: Demonstrates how to use the nearby_service plugin.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
- sdk: '>=3.0.6 <4.0.0'
+ sdk: '>=3.0.0 <4.0.0'
dependencies:
+ provider: ^6.1.1
+ file_picker: ^6.1.1
flutter:
sdk: flutter
nearby_service:
path: ../
- permission_handler: ^11.0.1
- provider: ^6.1.1
+
dev_dependencies:
diff --git a/ios/Classes/NearbyMessageConverter.swift b/ios/Classes/NearbyMessageConverter.swift
index 4f05f39..5aae884 100644
--- a/ios/Classes/NearbyMessageConverter.swift
+++ b/ios/Classes/NearbyMessageConverter.swift
@@ -41,7 +41,7 @@ class NearbyMessageConverter {
var result: String?
let jsonObject = [
- "message": message,
+ "content": ["value": message, "type": "text"],
"sender": ["id": peerID.displayName, "displayName": name],
] as [String : Any]
diff --git a/ios/Classes/NearbyServicePlugin.swift b/ios/Classes/NearbyServicePlugin.swift
index 8a03528..296156c 100644
--- a/ios/Classes/NearbyServicePlugin.swift
+++ b/ios/Classes/NearbyServicePlugin.swift
@@ -102,7 +102,8 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
}
case "send":
- if let message: String = getArgument(for: "message", call: call) {
+ if let content: Dictionary = getArgument(for: "content", call: call),
+ let message: String = content["value"] as? String {
if let receiver : Dictionary = getArgument(for: "receiver", call: call),
let receiverId : String = receiver["id"] as? String
{
diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart
index f8a24de..5324aa8 100644
--- a/lib/src/models/models.dart
+++ b/lib/src/models/models.dart
@@ -1,4 +1,6 @@
export 'nearby_device.dart';
export 'nearby_device_status.dart';
export 'nearby_message.dart';
+export 'nearby_message_content.dart';
export 'communication_channel_state.dart';
+export 'nearby_file.dart';
diff --git a/lib/src/models/nearby_device.dart b/lib/src/models/nearby_device.dart
index 9566276..afeabd5 100644
--- a/lib/src/models/nearby_device.dart
+++ b/lib/src/models/nearby_device.dart
@@ -147,4 +147,20 @@ class NearbyDeviceInfo {
'displayName': displayName,
};
}
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyDeviceInfo &&
+ runtimeType == other.runtimeType &&
+ displayName == other.displayName &&
+ id == other.id;
+
+ @override
+ int get hashCode => displayName.hashCode ^ id.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyDeviceInfo{displayName: $displayName, id: $id}';
+ }
}
diff --git a/lib/src/models/nearby_file.dart b/lib/src/models/nearby_file.dart
new file mode 100644
index 0000000..2ebfe81
--- /dev/null
+++ b/lib/src/models/nearby_file.dart
@@ -0,0 +1,47 @@
+import 'dart:io';
+import 'package:nearby_service/nearby_service.dart';
+
+///
+/// A representation of a file that can be got from the Nearby Service's
+/// communication channel.
+///
+/// You can use [id] to compare it to the id from [NearbyMessageFileContent.id].
+///
+/// From the communication channel, you usually get
+/// the [NearbyMessageFileContent] request first. After that, you get [NearbyFile].
+///
+class NearbyFile {
+ ///
+ /// Pass [content] assigned to file to be sent.
+ ///
+ const NearbyFile({
+ required this.content,
+ required this.file,
+ });
+
+ ///
+ /// Quick info about the file
+ ///
+ final NearbyMessageFileContent content;
+
+ ///
+ /// A file that you can save in your phone if needed
+ ///
+ final File file;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyFile &&
+ runtimeType == other.runtimeType &&
+ content == other.content &&
+ file == other.file;
+
+ @override
+ int get hashCode => content.hashCode ^ file.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyFile{content: $content, file: $file}';
+ }
+}
diff --git a/lib/src/models/nearby_message.dart b/lib/src/models/nearby_message.dart
index d1a9756..3df8f89 100644
--- a/lib/src/models/nearby_message.dart
+++ b/lib/src/models/nearby_message.dart
@@ -5,18 +5,21 @@ import 'package:nearby_service/nearby_service.dart';
///
abstract class NearbyMessage {
///
- /// The basic message contains only [value] - the content
+ /// The basic message contains only [content] - the content
/// to be sent or received.
///
- const NearbyMessage({required this.value});
-
- final String value;
+ const NearbyMessage({required this.content});
///
- /// Checks if [value] is not empty
+ /// Model representing content to be sent or received
+ ///
+ final NearbyMessageContent content;
+
+ ///
+ /// Checks if [content] is not empty
///
bool get isValid {
- return value.isNotEmpty;
+ return content.isValid;
}
@override
@@ -24,14 +27,14 @@ abstract class NearbyMessage {
identical(this, other) ||
other is NearbyMessage &&
runtimeType == other.runtimeType &&
- value == other.value;
+ content == other.content;
@override
- int get hashCode => value.hashCode;
+ int get hashCode => content.hashCode;
@override
String toString() {
- return 'NearbyMessage{value: $value}';
+ return 'NearbyMessage{content: $content}';
}
}
@@ -40,11 +43,11 @@ abstract class NearbyMessage {
///
class OutgoingNearbyMessage extends NearbyMessage {
///
- /// To send a message, in addition to [value], you need to pass [receiver]
+ /// To send a message, in addition to [content], you need to pass [receiver]
/// to know to whom the message is addressed.
///
const OutgoingNearbyMessage({
- required super.value,
+ required super.content,
required this.receiver,
});
@@ -58,7 +61,7 @@ class OutgoingNearbyMessage extends NearbyMessage {
///
Map toJson() {
return {
- 'message': value,
+ 'content': content.toJson(),
'receiver': receiver.toJson(),
};
}
@@ -73,6 +76,11 @@ class OutgoingNearbyMessage extends NearbyMessage {
@override
int get hashCode => super.hashCode ^ receiver.hashCode;
+
+ @override
+ String toString() {
+ return 'OutgoingNearbyMessage{receiver: $receiver content:$content}';
+ }
}
///
@@ -80,11 +88,11 @@ class OutgoingNearbyMessage extends NearbyMessage {
///
class ReceivedNearbyMessage extends NearbyMessage {
///
- /// The received message contains a [sender] in addition to [value],
+ /// The received message contains a [sender] in addition to [content],
/// to know from whom the message came.
///
const ReceivedNearbyMessage({
- required super.value,
+ required super.content,
required this.sender,
});
@@ -93,7 +101,7 @@ class ReceivedNearbyMessage extends NearbyMessage {
///
factory ReceivedNearbyMessage.fromJson(Map? json) {
return ReceivedNearbyMessage(
- value: json?['message'] ?? '',
+ content: NearbyMessageContent.fromJson(json?['content']),
sender: NearbyDeviceInfo.fromJson(json?['sender']),
);
}
@@ -116,6 +124,6 @@ class ReceivedNearbyMessage extends NearbyMessage {
@override
String toString() {
- return 'ReceivedNearbyMessage{sender: $sender}';
+ return 'ReceivedNearbyMessage{sender: $sender content: $content}';
}
}
diff --git a/lib/src/models/nearby_message_content.dart b/lib/src/models/nearby_message_content.dart
new file mode 100644
index 0000000..c7fb99a
--- /dev/null
+++ b/lib/src/models/nearby_message_content.dart
@@ -0,0 +1,221 @@
+import 'package:nearby_service/nearby_service.dart';
+import 'package:nearby_service/src/utils/random.dart';
+
+///
+/// Type of the message.
+///
+/// If [text], it will be a text message.
+/// If [file], it will be a file request. After accepting request,
+/// user can get file stream from connected device.
+///
+enum NearbyMessageContentType {
+ text,
+ file;
+
+ ///
+ /// Checks if this is [NearbyMessageContentType.text]
+ ///
+ bool get isText {
+ return this == NearbyMessageContentType.text;
+ }
+
+ ///
+ /// Checks if this is [NearbyMessageContentType.file]
+ ///
+ bool get isFile {
+ return this == NearbyMessageContentType.file;
+ }
+}
+
+///
+/// Abstraction for the message content.
+/// Contains [_type] to determine, what type of content is it.
+///
+abstract class NearbyMessageContent {
+ const NearbyMessageContent(this._type);
+
+ ///
+ /// Contains the conditional logic of creating [NearbyMessageFileContent]
+ /// or [NearbyMessageTextContent] by `type` field of [json].
+ ///
+ factory NearbyMessageContent.fromJson(Map? json) {
+ try {
+ final type = NearbyMessageContentType.values.firstWhere(
+ (e) => e.name == json?['type'],
+ );
+ if (type.isFile) {
+ return NearbyMessageFileContent.fromJson(json);
+ } else if (type.isText) {
+ return NearbyMessageTextContent.fromJson(json);
+ } else {
+ throw NearbyServiceException.unsupportedDecoding(json);
+ }
+ } catch (e) {
+ throw NearbyServiceException(e);
+ }
+ }
+
+ final NearbyMessageContentType _type;
+
+ ///
+ /// Check for the content if it is valid for sending or receiving.
+ ///
+ bool get isValid;
+
+ ///
+ /// * The [onText] callback returns this instance of [NearbyMessageContent],
+ /// cast as [NearbyMessageTextContent] if is a text.
+ ///
+ /// * The [onFile] callback returns this instance of [NearbyMessageContent],
+ /// cast as [NearbyMessageFileContent] if is a file.
+ ///
+ T? get({
+ T Function(NearbyMessageTextContent)? onText,
+ T Function(NearbyMessageFileContent)? onFile,
+ }) {
+ if (this is NearbyMessageTextContent && onText != null) {
+ return onText(this as NearbyMessageTextContent);
+ } else if (this is NearbyMessageFileContent && onFile != null) {
+ return onFile(this as NearbyMessageFileContent);
+ }
+ return null;
+ }
+
+ ///
+ /// Gets [Map] from [NearbyMessageContent]
+ ///
+ Map toJson() {
+ return {'type': _type.name};
+ }
+}
+
+///
+/// Nearby message Text content.
+///
+/// Contains [value] - the message to be sent or received.
+///
+class NearbyMessageTextContent extends NearbyMessageContent {
+ const NearbyMessageTextContent({required this.value})
+ : super(
+ NearbyMessageContentType.text,
+ );
+
+ ///
+ /// Gets [NearbyMessageTextContent] from [json]
+ ///
+ factory NearbyMessageTextContent.fromJson(Map? json) {
+ return NearbyMessageTextContent(
+ value: json?['value'] ?? '',
+ );
+ }
+
+ ///
+ /// The message to be sent or received
+ ///
+ final String value;
+
+ @override
+ bool get isValid => value.isNotEmpty;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyMessageTextContent &&
+ runtimeType == other.runtimeType &&
+ value == other.value;
+
+ @override
+ int get hashCode => value.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyMessageTextContent{value: $value}';
+ }
+
+ @override
+ Map toJson() {
+ return {
+ 'value': value,
+ ...super.toJson(),
+ };
+ }
+}
+
+///
+/// Nearby message File content. Used for file sending requests.
+/// Does not contain file bytes!
+///
+/// Contains [filePath] - the name of file to be sent or received.
+///
+class NearbyMessageFileContent extends NearbyMessageContent {
+ const NearbyMessageFileContent._({
+ required this.id,
+ required this.filePath,
+ }) : super(
+ NearbyMessageContentType.file,
+ );
+
+ ///
+ /// Basic constructor with [filePath] to be sent or received.
+ ///
+ NearbyMessageFileContent({required this.filePath})
+ : id = RandomUtils.instance.nextInt(1000000, 9999999).toString(),
+ super(
+ NearbyMessageContentType.file,
+ );
+
+ ///
+ /// Gets [NearbyMessageFileContent] from [json].
+ ///
+ factory NearbyMessageFileContent.fromJson(Map? json) {
+ return NearbyMessageFileContent._(
+ id: json?['id'] ?? '',
+ filePath: json?['filePath'] ?? '',
+ );
+ }
+
+ ///
+ /// ID for comparing file requests.
+ ///
+ final String id;
+
+ ///
+ /// The name of the file to be sent or received.
+ ///
+ final String filePath;
+
+ String get fileName {
+ try {
+ return filePath.split('/').last;
+ } catch (e) {
+ throw NearbyServiceException('Can\'t get fileName from $filePath');
+ }
+ }
+
+ @override
+ bool get isValid => filePath.isNotEmpty;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyMessageFileContent &&
+ runtimeType == other.runtimeType &&
+ filePath == other.filePath;
+
+ @override
+ int get hashCode => filePath.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyMessageFileContent{filePath: $filePath, id: $id}';
+ }
+
+ @override
+ Map toJson() {
+ return {
+ 'id': id,
+ 'filePath': filePath,
+ ...super.toJson(),
+ };
+ }
+}
diff --git a/lib/src/platforms/android/socket_service/file_creator.dart b/lib/src/platforms/android/socket_service/file_creator.dart
new file mode 100644
index 0000000..57531ee
--- /dev/null
+++ b/lib/src/platforms/android/socket_service/file_creator.dart
@@ -0,0 +1,31 @@
+import 'dart:io';
+
+import 'package:nearby_service/nearby_service.dart';
+import 'package:path_provider/path_provider.dart';
+
+class FileCreator {
+ FileCreator({required this.content});
+
+ static const _finishCommand = '@@FINISH_SENDING_FILE_';
+
+ static String generateFinishCommand(String id) {
+ return '$_finishCommand$id';
+ }
+
+ final NearbyMessageFileContent content;
+ final _bytes = [];
+
+ String get finishCommand => '$_finishCommand${content.id}';
+
+ void add(List value) {
+ _bytes.addAll(value);
+ }
+
+ Future getFile() async {
+ final directory = await getTemporaryDirectory();
+ final file = File('${directory.path}/${content.fileName}');
+
+ await file.writeAsBytes(_bytes);
+ return NearbyFile(file: file, content: content);
+ }
+}
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 602a98f..4e67e38 100644
--- a/lib/src/platforms/android/socket_service/nearby_socket_service.dart
+++ b/lib/src/platforms/android/socket_service/nearby_socket_service.dart
@@ -1,11 +1,12 @@
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/platforms/android/socket_service/file_creator.dart';
import 'package:nearby_service/src/utils/logger.dart';
+import 'package:nearby_service/src/utils/random.dart';
import 'package:nearby_service/src/utils/stream_mapper.dart';
part 'ping_manager.dart';
@@ -25,10 +26,11 @@ class NearbySocketService {
final state = ValueNotifier(CommunicationChannelState.notConnected);
NearbyConnectionAndroidInfo? connectionInfo;
+ FileCreator? fileCreator;
String? _connectedDeviceId;
WebSocket? _socket;
HttpServer? _server;
- StreamSubscription? _messagesSubscription;
+ StreamSubscription? _streamSubscription;
///
/// Start a socket with the user's role defined.
@@ -80,27 +82,41 @@ class NearbySocketService {
_socket!.add(
jsonEncode(
{
- 'message': message.value,
+ 'content': message.content.toJson(),
'sender': sender.toJson(),
},
),
);
+ message.content.get(
+ onFile: (fileContent) {
+ final file = File(fileContent.filePath);
+
+ file.openRead().listen(
+ (data) => _socket?.add(data),
+ onDone: () {
+ _socket?.add(
+ FileCreator.generateFinishCommand(fileContent.id),
+ );
+ },
+ );
+ },
+ );
}
return true;
}
return false;
} else {
- throw NearbyServiceException.invalidMessage(message.value);
+ throw NearbyServiceException.invalidMessage(message.content);
}
}
///
- /// Turns off [_messagesSubscription] and [_socket].
+ /// Turns off [_streamSubscription] and [_socket].
///
Future cancel() async {
try {
- await _messagesSubscription?.cancel();
- _messagesSubscription = null;
+ await _streamSubscription?.cancel();
+ _streamSubscription = null;
_socket?.close();
_socket = null;
_server?.close(force: true);
@@ -182,13 +198,40 @@ class NearbySocketService {
Logger.debug('Starting socket subscription');
if (_connectedDeviceId != null) {
- _messagesSubscription = _socket
- ?.map(MessagesStreamMapper.toMessage)
- .where((event) => event != null)
- .cast()
- .map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!))
- .listen(
- socketListener.onData,
+ _streamSubscription = _socket?.listen(
+ (event) async {
+ if (fileCreator != null) {
+ if (event is List) {
+ fileCreator!.add(event);
+ } else if (event == fileCreator?.finishCommand) {
+ final file = await fileCreator!.getFile().whenComplete(
+ () {
+ fileCreator = null;
+ },
+ );
+ socketListener.onFile?.call(file);
+ }
+ } else {
+ try {
+ final message = MessagesStreamMapper.toMessage(event);
+ if (message != null) {
+ final newMessage = MessagesStreamMapper.replaceId(
+ message,
+ _connectedDeviceId!,
+ );
+ newMessage.content.get(
+ onFile: (fileContent) {
+ fileCreator = FileCreator(content: fileContent);
+ },
+ );
+
+ socketListener.onMessage(newMessage);
+ }
+ } catch (e) {
+ Logger.error(e);
+ }
+ }
+ },
onDone: () {
state.value = CommunicationChannelState.notConnected;
socketListener.onDone?.call();
@@ -201,10 +244,10 @@ class NearbySocketService {
cancelOnError: socketListener.cancelOnError,
);
}
- if (_messagesSubscription != null) {
+ if (_streamSubscription != null) {
state.value = CommunicationChannelState.connected;
Logger.info('Socket subscription was created successfully');
- socketListener.onCreated?.call(_messagesSubscription!);
+ socketListener.onCreated?.call();
} else {
state.value = CommunicationChannelState.notConnected;
}
diff --git a/lib/src/platforms/android/socket_service/network.dart b/lib/src/platforms/android/socket_service/network.dart
index ab6d618..c5f6f5a 100644
--- a/lib/src/platforms/android/socket_service/network.dart
+++ b/lib/src/platforms/android/socket_service/network.dart
@@ -11,7 +11,6 @@ class _Urls {
class NearbyServiceNetwork {
final _httpClient = HttpClient();
- final _random = Random();
Future pingServer({
required String address,
@@ -60,7 +59,7 @@ class NearbyServiceNetwork {
required int port,
}) async {
try {
- final connectionId = _random.nextInt(1000) + 100;
+ final connectionId = RandomUtils.instance.nextInt(100, 999);
final url =
'${_Protocols.ws}$ownerIpAddress:$port${_Urls.ws}?as=$connectionId';
Logger.debug('Connecting to $url');
diff --git a/lib/src/platforms/ios/nearby_ios_service.dart b/lib/src/platforms/ios/nearby_ios_service.dart
index 012aa02..ae0943f 100644
--- a/lib/src/platforms/ios/nearby_ios_service.dart
+++ b/lib/src/platforms/ios/nearby_ios_service.dart
@@ -16,7 +16,7 @@ class NearbyIOSService extends NearbyService {
final _isBrowser = ValueNotifier(true);
final _state = ValueNotifier(CommunicationChannelState.notConnected);
- StreamSubscription? _messagesSubscription;
+ StreamSubscription? _messagesSubscription;
@override
ValueListenable get communicationChannelState =>
@@ -165,12 +165,25 @@ class NearbyIOSService extends NearbyService {
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()
+ // .map(MessagesStreamMapper.toMessage)
+ // .where((event) => event?.sender.id == data.connectedDeviceId)
+ // .where((event) => event != null)
+ // .cast()
.listen(
- eventListener.onData,
+ (event) {
+ try {
+ final message = MessagesStreamMapper.toMessage(event);
+ if (message != null && message.sender.id == data.connectedDeviceId) {
+ eventListener.onMessage(message);
+ }
+ } catch (e) {
+ try {
+ eventListener.onFile?.call(event);
+ } catch (e) {
+ Logger.error(e);
+ }
+ }
+ },
onDone: () {
_state.value = CommunicationChannelState.notConnected;
eventListener.onDone?.call();
@@ -184,7 +197,7 @@ class NearbyIOSService extends NearbyService {
);
if (_messagesSubscription != null) {
Logger.info('Messages subscription was created successfully');
- eventListener.onCreated?.call(_messagesSubscription!);
+ eventListener.onCreated?.call();
_state.value = CommunicationChannelState.connected;
} else {
_state.value = CommunicationChannelState.notConnected;
@@ -215,7 +228,7 @@ class NearbyIOSService extends NearbyService {
if (message.isValid) {
return NearbyServiceIOSPlatform.instance.send(message);
}
- throw NearbyServiceException.invalidMessage(message.value);
+ throw NearbyServiceException.invalidMessage(message.content);
}
///
diff --git a/lib/src/types/nearby_service_stream_listener.dart b/lib/src/types/nearby_service_stream_listener.dart
index 8a2363f..05a1c88 100644
--- a/lib/src/types/nearby_service_stream_listener.dart
+++ b/lib/src/types/nearby_service_stream_listener.dart
@@ -1,5 +1,3 @@
-import 'dart:async';
-
import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
@@ -8,19 +6,21 @@ import 'package:nearby_service/nearby_service.dart';
///
class NearbyServiceStreamListener {
///
- /// It is required to pass the [onData] parameter to process the
+ /// It is required to pass the [onMessage] parameter to process the
/// data that came through the stream.
///
const NearbyServiceStreamListener({
- required this.onData,
+ required this.onMessage,
+ this.onFile,
this.onCreated,
this.onDone,
this.onError,
this.cancelOnError,
});
- final ValueChanged onData;
- final ValueChanged>? onCreated;
+ final ValueChanged onMessage;
+ final ValueChanged? onFile;
+ final VoidCallback? onCreated;
final VoidCallback? onDone;
final void Function(Object, [StackTrace])? onError;
final bool? cancelOnError;
diff --git a/lib/src/utils/exception.dart b/lib/src/utils/exception.dart
index ddafce7..7145801 100644
--- a/lib/src/utils/exception.dart
+++ b/lib/src/utils/exception.dart
@@ -1,5 +1,6 @@
import 'dart:io';
+import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart';
///
@@ -30,9 +31,9 @@ class NearbyServiceException implements Exception {
);
}
- factory NearbyServiceException.invalidMessage(String value) {
+ factory NearbyServiceException.invalidMessage(NearbyMessageContent content) {
return NearbyServiceException(
- 'The message="$value" is not valid',
+ 'The message="$content" is not valid',
);
}
diff --git a/lib/src/utils/random.dart b/lib/src/utils/random.dart
new file mode 100644
index 0000000..f59fd33
--- /dev/null
+++ b/lib/src/utils/random.dart
@@ -0,0 +1,15 @@
+import 'dart:math';
+
+class RandomUtils {
+ static RandomUtils? _instance;
+
+ static RandomUtils get instance {
+ return _instance ?? RandomUtils();
+ }
+
+ final _random = Random();
+
+ int nextInt(int min, int max) {
+ return _random.nextInt(max - min) + min;
+ }
+}
diff --git a/lib/src/utils/stream_mapper.dart b/lib/src/utils/stream_mapper.dart
index 51096a7..7da4b11 100644
--- a/lib/src/utils/stream_mapper.dart
+++ b/lib/src/utils/stream_mapper.dart
@@ -9,7 +9,7 @@ abstract class MessagesStreamMapper {
String id,
) {
return ReceivedNearbyMessage(
- value: message.value,
+ content: message.content,
sender: NearbyDeviceInfo(
id: id,
displayName: message.sender.displayName,
diff --git a/pubspec.yaml b/pubspec.yaml
index 8d4943c..5b7f93b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -14,6 +14,7 @@ environment:
dependencies:
flutter:
sdk: flutter
+ path_provider: ^2.1.2
plugin_platform_interface: ^2.0.2
dev_dependencies: