feat(android): implement file sending logic
This commit is contained in:
@@ -27,7 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "com.xenikii.nearby_service_example"
|
namespace "com.xenikii.nearby_service_example"
|
||||||
compileSdk flutter.compileSdkVersion
|
compileSdk 34
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
ext.kotlin_version = '1.9.22'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
@@ -26,6 +26,10 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|||||||
@@ -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
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@@ -11,6 +12,8 @@ import 'components/app_snack_bar.dart';
|
|||||||
|
|
||||||
part 'components/action_button.dart';
|
part 'components/action_button.dart';
|
||||||
|
|
||||||
|
part 'components/action_dialog.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
final service = AppService();
|
final service = AppService();
|
||||||
@@ -197,6 +200,8 @@ class AppService extends ChangeNotifier {
|
|||||||
StreamSubscription? peersSubscription;
|
StreamSubscription? peersSubscription;
|
||||||
StreamSubscription? connectedDeviceSubscription;
|
StreamSubscription? connectedDeviceSubscription;
|
||||||
|
|
||||||
|
final _filesAccepts = <String, Future<bool?>>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
stopListeningAll();
|
stopListeningAll();
|
||||||
@@ -373,14 +378,27 @@ class AppService extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> startCommunicationChannel({
|
Future<void> startCommunicationChannel({
|
||||||
ValueChanged<ReceivedNearbyMessage>? listener,
|
ValueChanged<ReceivedNearbyMessage>? listener,
|
||||||
|
ValueChanged<File>? onFileSaved,
|
||||||
}) async {
|
}) async {
|
||||||
final eventListener = NearbyServiceStreamListener(
|
final eventListener = NearbyServiceStreamListener(
|
||||||
onCreated: (_) {
|
onCreated: () {
|
||||||
updateState(AppState.communicationChannelCreated);
|
updateState(AppState.communicationChannelCreated);
|
||||||
},
|
},
|
||||||
onData: (event) {
|
onMessage: (event) {
|
||||||
listener?.call(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]) {
|
onError: (e, [StackTrace? s]) {
|
||||||
stopListeningAll();
|
stopListeningAll();
|
||||||
},
|
},
|
||||||
@@ -394,16 +412,30 @@ class AppService extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send(String message) {
|
void sendMessage(String message) {
|
||||||
if (connectedDevice == null) return;
|
if (connectedDevice == null) return;
|
||||||
_nearbyService.send(
|
_nearbyService.send(
|
||||||
OutgoingNearbyMessage(
|
OutgoingNearbyMessage(
|
||||||
value: message,
|
content: NearbyMessageTextContent(value: message),
|
||||||
receiver: connectedDevice!.info,
|
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 {
|
Future<void> disconnect(NearbyDevice device) async {
|
||||||
try {
|
try {
|
||||||
await _nearbyService.disconnect(device);
|
await _nearbyService.disconnect(device);
|
||||||
@@ -740,15 +772,11 @@ class _ConnectedBody extends StatelessWidget {
|
|||||||
if (service.communicationChannelState !=
|
if (service.communicationChannelState !=
|
||||||
CommunicationChannelState.loading)
|
CommunicationChannelState.loading)
|
||||||
_ActionButton(
|
_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',
|
title: 'Start communicate',
|
||||||
|
onTap: () => service.startCommunicationChannel(
|
||||||
|
listener: (event) => _listener(context, event),
|
||||||
|
onFileSaved: (file) => _onFileSaved(context, file),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Text(
|
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 {
|
class _ConnectedSocketBody extends StatefulWidget {
|
||||||
@@ -773,6 +832,7 @@ class _ConnectedSocketBody extends StatefulWidget {
|
|||||||
|
|
||||||
class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
|
class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
|
||||||
String message = '';
|
String message = '';
|
||||||
|
String filePath = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -797,6 +857,7 @@ class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onChanged: (value) => setState(() {
|
onChanged: (value) => setState(() {
|
||||||
message = value;
|
message = value;
|
||||||
@@ -810,15 +871,51 @@ class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
_ActionButton(
|
Flexible(
|
||||||
title: 'Send',
|
child: _ActionButton(
|
||||||
onTap: () {
|
title: 'Send',
|
||||||
service.send(message);
|
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'),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.6 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
provider: ^6.1.1
|
||||||
|
file_picker: ^6.1.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
nearby_service:
|
nearby_service:
|
||||||
path: ../
|
path: ../
|
||||||
permission_handler: ^11.0.1
|
|
||||||
provider: ^6.1.1
|
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class NearbyMessageConverter {
|
|||||||
|
|
||||||
var result: String?
|
var result: String?
|
||||||
let jsonObject = [
|
let jsonObject = [
|
||||||
"message": message,
|
"content": ["value": message, "type": "text"],
|
||||||
"sender": ["id": peerID.displayName, "displayName": name],
|
"sender": ["id": peerID.displayName, "displayName": name],
|
||||||
] as [String : Any]
|
] as [String : Any]
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "send":
|
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),
|
if let receiver : Dictionary<String, AnyObject> = getArgument(for: "receiver", call: call),
|
||||||
let receiverId : String = receiver["id"] as? String
|
let receiverId : String = receiver["id"] as? String
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export 'nearby_device.dart';
|
export 'nearby_device.dart';
|
||||||
export 'nearby_device_status.dart';
|
export 'nearby_device_status.dart';
|
||||||
export 'nearby_message.dart';
|
export 'nearby_message.dart';
|
||||||
|
export 'nearby_message_content.dart';
|
||||||
export 'communication_channel_state.dart';
|
export 'communication_channel_state.dart';
|
||||||
|
export 'nearby_file.dart';
|
||||||
|
|||||||
@@ -147,4 +147,20 @@ class NearbyDeviceInfo {
|
|||||||
'displayName': displayName,
|
'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}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,18 +5,21 @@ import 'package:nearby_service/nearby_service.dart';
|
|||||||
///
|
///
|
||||||
abstract class NearbyMessage {
|
abstract class NearbyMessage {
|
||||||
///
|
///
|
||||||
/// The basic message contains only [value] - the content
|
/// The basic message contains only [content] - the content
|
||||||
/// to be sent or received.
|
/// to be sent or received.
|
||||||
///
|
///
|
||||||
const NearbyMessage({required this.value});
|
const NearbyMessage({required this.content});
|
||||||
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// 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 {
|
bool get isValid {
|
||||||
return value.isNotEmpty;
|
return content.isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -24,14 +27,14 @@ abstract class NearbyMessage {
|
|||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is NearbyMessage &&
|
other is NearbyMessage &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
value == other.value;
|
content == other.content;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => value.hashCode;
|
int get hashCode => content.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'NearbyMessage{value: $value}';
|
return 'NearbyMessage{content: $content}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,11 +43,11 @@ abstract class NearbyMessage {
|
|||||||
///
|
///
|
||||||
class OutgoingNearbyMessage extends 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.
|
/// to know to whom the message is addressed.
|
||||||
///
|
///
|
||||||
const OutgoingNearbyMessage({
|
const OutgoingNearbyMessage({
|
||||||
required super.value,
|
required super.content,
|
||||||
required this.receiver,
|
required this.receiver,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ class OutgoingNearbyMessage extends NearbyMessage {
|
|||||||
///
|
///
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'message': value,
|
'content': content.toJson(),
|
||||||
'receiver': receiver.toJson(),
|
'receiver': receiver.toJson(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -73,6 +76,11 @@ class OutgoingNearbyMessage extends NearbyMessage {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => super.hashCode ^ receiver.hashCode;
|
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 {
|
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.
|
/// to know from whom the message came.
|
||||||
///
|
///
|
||||||
const ReceivedNearbyMessage({
|
const ReceivedNearbyMessage({
|
||||||
required super.value,
|
required super.content,
|
||||||
required this.sender,
|
required this.sender,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -93,7 +101,7 @@ class ReceivedNearbyMessage extends NearbyMessage {
|
|||||||
///
|
///
|
||||||
factory ReceivedNearbyMessage.fromJson(Map<String, dynamic>? json) {
|
factory ReceivedNearbyMessage.fromJson(Map<String, dynamic>? json) {
|
||||||
return ReceivedNearbyMessage(
|
return ReceivedNearbyMessage(
|
||||||
value: json?['message'] ?? '',
|
content: NearbyMessageContent.fromJson(json?['content']),
|
||||||
sender: NearbyDeviceInfo.fromJson(json?['sender']),
|
sender: NearbyDeviceInfo.fromJson(json?['sender']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -116,6 +124,6 @@ class ReceivedNearbyMessage extends NearbyMessage {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ReceivedNearbyMessage{sender: $sender}';
|
return 'ReceivedNearbyMessage{sender: $sender content: $content}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:nearby_service/nearby_service.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/logger.dart';
|
||||||
|
import 'package:nearby_service/src/utils/random.dart';
|
||||||
import 'package:nearby_service/src/utils/stream_mapper.dart';
|
import 'package:nearby_service/src/utils/stream_mapper.dart';
|
||||||
|
|
||||||
part 'ping_manager.dart';
|
part 'ping_manager.dart';
|
||||||
@@ -25,10 +26,11 @@ class NearbySocketService {
|
|||||||
final state = ValueNotifier(CommunicationChannelState.notConnected);
|
final state = ValueNotifier(CommunicationChannelState.notConnected);
|
||||||
NearbyConnectionAndroidInfo? connectionInfo;
|
NearbyConnectionAndroidInfo? connectionInfo;
|
||||||
|
|
||||||
|
FileCreator? fileCreator;
|
||||||
String? _connectedDeviceId;
|
String? _connectedDeviceId;
|
||||||
WebSocket? _socket;
|
WebSocket? _socket;
|
||||||
HttpServer? _server;
|
HttpServer? _server;
|
||||||
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
|
StreamSubscription? _streamSubscription;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Start a socket with the user's role defined.
|
/// Start a socket with the user's role defined.
|
||||||
@@ -80,27 +82,41 @@ class NearbySocketService {
|
|||||||
_socket!.add(
|
_socket!.add(
|
||||||
jsonEncode(
|
jsonEncode(
|
||||||
{
|
{
|
||||||
'message': message.value,
|
'content': message.content.toJson(),
|
||||||
'sender': sender.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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} 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 {
|
Future<bool> cancel() async {
|
||||||
try {
|
try {
|
||||||
await _messagesSubscription?.cancel();
|
await _streamSubscription?.cancel();
|
||||||
_messagesSubscription = null;
|
_streamSubscription = null;
|
||||||
_socket?.close();
|
_socket?.close();
|
||||||
_socket = null;
|
_socket = null;
|
||||||
_server?.close(force: true);
|
_server?.close(force: true);
|
||||||
@@ -182,13 +198,40 @@ class NearbySocketService {
|
|||||||
Logger.debug('Starting socket subscription');
|
Logger.debug('Starting socket subscription');
|
||||||
|
|
||||||
if (_connectedDeviceId != null) {
|
if (_connectedDeviceId != null) {
|
||||||
_messagesSubscription = _socket
|
_streamSubscription = _socket?.listen(
|
||||||
?.map(MessagesStreamMapper.toMessage)
|
(event) async {
|
||||||
.where((event) => event != null)
|
if (fileCreator != null) {
|
||||||
.cast<ReceivedNearbyMessage>()
|
if (event is List<int>) {
|
||||||
.map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!))
|
fileCreator!.add(event);
|
||||||
.listen(
|
} else if (event == fileCreator?.finishCommand) {
|
||||||
socketListener.onData,
|
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: () {
|
onDone: () {
|
||||||
state.value = CommunicationChannelState.notConnected;
|
state.value = CommunicationChannelState.notConnected;
|
||||||
socketListener.onDone?.call();
|
socketListener.onDone?.call();
|
||||||
@@ -201,10 +244,10 @@ class NearbySocketService {
|
|||||||
cancelOnError: socketListener.cancelOnError,
|
cancelOnError: socketListener.cancelOnError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_messagesSubscription != null) {
|
if (_streamSubscription != null) {
|
||||||
state.value = CommunicationChannelState.connected;
|
state.value = CommunicationChannelState.connected;
|
||||||
Logger.info('Socket subscription was created successfully');
|
Logger.info('Socket subscription was created successfully');
|
||||||
socketListener.onCreated?.call(_messagesSubscription!);
|
socketListener.onCreated?.call();
|
||||||
} else {
|
} else {
|
||||||
state.value = CommunicationChannelState.notConnected;
|
state.value = CommunicationChannelState.notConnected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class _Urls {
|
|||||||
|
|
||||||
class NearbyServiceNetwork {
|
class NearbyServiceNetwork {
|
||||||
final _httpClient = HttpClient();
|
final _httpClient = HttpClient();
|
||||||
final _random = Random();
|
|
||||||
|
|
||||||
Future<HttpClientResponse?> pingServer({
|
Future<HttpClientResponse?> pingServer({
|
||||||
required String address,
|
required String address,
|
||||||
@@ -60,7 +59,7 @@ class NearbyServiceNetwork {
|
|||||||
required int port,
|
required int port,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final connectionId = _random.nextInt(1000) + 100;
|
final connectionId = RandomUtils.instance.nextInt(100, 999);
|
||||||
final url =
|
final url =
|
||||||
'${_Protocols.ws}$ownerIpAddress:$port${_Urls.ws}?as=$connectionId';
|
'${_Protocols.ws}$ownerIpAddress:$port${_Urls.ws}?as=$connectionId';
|
||||||
Logger.debug('Connecting to $url');
|
Logger.debug('Connecting to $url');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class NearbyIOSService extends NearbyService {
|
|||||||
final _isBrowser = ValueNotifier<bool>(true);
|
final _isBrowser = ValueNotifier<bool>(true);
|
||||||
final _state = ValueNotifier(CommunicationChannelState.notConnected);
|
final _state = ValueNotifier(CommunicationChannelState.notConnected);
|
||||||
|
|
||||||
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
|
StreamSubscription? _messagesSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ValueListenable<CommunicationChannelState> get communicationChannelState =>
|
ValueListenable<CommunicationChannelState> get communicationChannelState =>
|
||||||
@@ -165,12 +165,25 @@ class NearbyIOSService extends NearbyService {
|
|||||||
await endCommunicationChannel();
|
await endCommunicationChannel();
|
||||||
final eventListener = data.eventListener;
|
final eventListener = data.eventListener;
|
||||||
_messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream
|
_messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream
|
||||||
.map(MessagesStreamMapper.toMessage)
|
// .map(MessagesStreamMapper.toMessage)
|
||||||
.where((event) => event?.sender.id == data.connectedDeviceId)
|
// .where((event) => event?.sender.id == data.connectedDeviceId)
|
||||||
.where((event) => event != null)
|
// .where((event) => event != null)
|
||||||
.cast<ReceivedNearbyMessage>()
|
// .cast<ReceivedNearbyMessage>()
|
||||||
.listen(
|
.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: () {
|
onDone: () {
|
||||||
_state.value = CommunicationChannelState.notConnected;
|
_state.value = CommunicationChannelState.notConnected;
|
||||||
eventListener.onDone?.call();
|
eventListener.onDone?.call();
|
||||||
@@ -184,7 +197,7 @@ class NearbyIOSService extends NearbyService {
|
|||||||
);
|
);
|
||||||
if (_messagesSubscription != null) {
|
if (_messagesSubscription != null) {
|
||||||
Logger.info('Messages subscription was created successfully');
|
Logger.info('Messages subscription was created successfully');
|
||||||
eventListener.onCreated?.call(_messagesSubscription!);
|
eventListener.onCreated?.call();
|
||||||
_state.value = CommunicationChannelState.connected;
|
_state.value = CommunicationChannelState.connected;
|
||||||
} else {
|
} else {
|
||||||
_state.value = CommunicationChannelState.notConnected;
|
_state.value = CommunicationChannelState.notConnected;
|
||||||
@@ -215,7 +228,7 @@ class NearbyIOSService extends NearbyService {
|
|||||||
if (message.isValid) {
|
if (message.isValid) {
|
||||||
return NearbyServiceIOSPlatform.instance.send(message);
|
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:flutter/foundation.dart';
|
||||||
import 'package:nearby_service/nearby_service.dart';
|
import 'package:nearby_service/nearby_service.dart';
|
||||||
|
|
||||||
@@ -8,19 +6,21 @@ import 'package:nearby_service/nearby_service.dart';
|
|||||||
///
|
///
|
||||||
class NearbyServiceStreamListener {
|
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.
|
/// data that came through the stream.
|
||||||
///
|
///
|
||||||
const NearbyServiceStreamListener({
|
const NearbyServiceStreamListener({
|
||||||
required this.onData,
|
required this.onMessage,
|
||||||
|
this.onFile,
|
||||||
this.onCreated,
|
this.onCreated,
|
||||||
this.onDone,
|
this.onDone,
|
||||||
this.onError,
|
this.onError,
|
||||||
this.cancelOnError,
|
this.cancelOnError,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ValueChanged<ReceivedNearbyMessage> onData;
|
final ValueChanged<ReceivedNearbyMessage> onMessage;
|
||||||
final ValueChanged<StreamSubscription<ReceivedNearbyMessage>>? onCreated;
|
final ValueChanged<NearbyFile>? onFile;
|
||||||
|
final VoidCallback? onCreated;
|
||||||
final VoidCallback? onDone;
|
final VoidCallback? onDone;
|
||||||
final void Function(Object, [StackTrace])? onError;
|
final void Function(Object, [StackTrace])? onError;
|
||||||
final bool? cancelOnError;
|
final bool? cancelOnError;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:nearby_service/nearby_service.dart';
|
||||||
import 'package:nearby_service/src/utils/logger.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(
|
return NearbyServiceException(
|
||||||
'The message="$value" is not valid',
|
'The message="$content" is not valid',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ abstract class MessagesStreamMapper {
|
|||||||
String id,
|
String id,
|
||||||
) {
|
) {
|
||||||
return ReceivedNearbyMessage(
|
return ReceivedNearbyMessage(
|
||||||
value: message.value,
|
content: message.content,
|
||||||
sender: NearbyDeviceInfo(
|
sender: NearbyDeviceInfo(
|
||||||
id: id,
|
id: id,
|
||||||
displayName: message.sender.displayName,
|
displayName: message.sender.displayName,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
path_provider: ^2.1.2
|
||||||
plugin_platform_interface: ^2.0.2
|
plugin_platform_interface: ^2.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user