feat(android): implement file sending logic

This commit is contained in:
ksenia312
2024-02-01 17:27:00 +01:00
parent 5870dc2577
commit 46451ef447
22 changed files with 606 additions and 75 deletions
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.9.22'
repositories {
google()
mavenCentral()
+4
View File
@@ -26,6 +26,10 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
+31
View File
@@ -0,0 +1,31 @@
part of '../main.dart';
class ActionDialog {
ActionDialog._();
static Future<bool?> show(
BuildContext context, {
required String title,
required String subtitle,
}) {
return showDialog<bool>(
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'),
),
],
);
},
);
}
}
+114 -17
View File
@@ -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<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final service = AppService();
@@ -197,6 +200,8 @@ class AppService extends ChangeNotifier {
StreamSubscription? peersSubscription;
StreamSubscription? connectedDeviceSubscription;
final _filesAccepts = <String, Future<bool?>>{};
@override
void dispose() {
stopListeningAll();
@@ -373,14 +378,27 @@ class AppService extends ChangeNotifier {
Future<void> startCommunicationChannel({
ValueChanged<ReceivedNearbyMessage>? listener,
ValueChanged<File>? 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<bool?> future) {
_filesAccepts[id] = future;
}
Future<void> 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<AppService>().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'),
],
);
},
+4 -3
View File
@@ -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:
+1 -1
View File
@@ -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]
+2 -1
View File
@@ -102,7 +102,8 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
}
case "send":
if let message: String = getArgument(for: "message", call: call) {
if let content: Dictionary<String, AnyObject> = getArgument(for: "content", call: call),
let message: String = content["value"] as? String {
if let receiver : Dictionary<String, AnyObject> = getArgument(for: "receiver", call: call),
let receiverId : String = receiver["id"] as? String
{
+2
View File
@@ -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';
+16
View File
@@ -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}';
}
}
+47
View File
@@ -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}';
}
}
+24 -16
View File
@@ -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<String, dynamic> 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<String, dynamic>? 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}';
}
}
+221
View File
@@ -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<String, dynamic>? 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>({
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<String, dynamic> 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<String, dynamic>? 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<String, dynamic> 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<String, dynamic>? 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<String, dynamic> toJson() {
return {
'id': id,
'filePath': filePath,
...super.toJson(),
};
}
}
@@ -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 = <int>[];
String get finishCommand => '$_finishCommand${content.id}';
void add(List<int> value) {
_bytes.addAll(value);
}
Future<NearbyFile> getFile() async {
final directory = await getTemporaryDirectory();
final file = File('${directory.path}/${content.fileName}');
await file.writeAsBytes(_bytes);
return NearbyFile(file: file, content: content);
}
}
@@ -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<ReceivedNearbyMessage>? _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<bool> 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<ReceivedNearbyMessage>()
.map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!))
.listen(
socketListener.onData,
_streamSubscription = _socket?.listen(
(event) async {
if (fileCreator != null) {
if (event is List<int>) {
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;
}
@@ -11,7 +11,6 @@ class _Urls {
class NearbyServiceNetwork {
final _httpClient = HttpClient();
final _random = Random();
Future<HttpClientResponse?> 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');
+21 -8
View File
@@ -16,7 +16,7 @@ class NearbyIOSService extends NearbyService {
final _isBrowser = ValueNotifier<bool>(true);
final _state = ValueNotifier(CommunicationChannelState.notConnected);
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
StreamSubscription? _messagesSubscription;
@override
ValueListenable<CommunicationChannelState> 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<ReceivedNearbyMessage>()
// .map(MessagesStreamMapper.toMessage)
// .where((event) => event?.sender.id == data.connectedDeviceId)
// .where((event) => event != null)
// .cast<ReceivedNearbyMessage>()
.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);
}
///
@@ -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<ReceivedNearbyMessage> onData;
final ValueChanged<StreamSubscription<ReceivedNearbyMessage>>? onCreated;
final ValueChanged<ReceivedNearbyMessage> onMessage;
final ValueChanged<NearbyFile>? onFile;
final VoidCallback? onCreated;
final VoidCallback? onDone;
final void Function(Object, [StackTrace])? onError;
final bool? cancelOnError;
+3 -2
View File
@@ -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',
);
}
+15
View File
@@ -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;
}
}
+1 -1
View File
@@ -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,
+1
View File
@@ -14,6 +14,7 @@ environment:
dependencies:
flutter:
sdk: flutter
path_provider: ^2.1.2
plugin_platform_interface: ^2.0.2
dev_dependencies: