feat(lib, android, ios): permissions clean up, add id to files pack

This commit is contained in:
ksenia312
2024-02-07 13:30:34 +01:00
parent 2541aa57d2
commit f18065db49
20 changed files with 313 additions and 220 deletions
+5 -1
View File
@@ -1,7 +1,11 @@
# Nearby Service
Nearby Service Flutter Plugin for creating connections in a P2P network. With this plugin you can easily create any
application for sharing information **without the Internet connection**.
<h3>Connecting phones in a P2P network</h3>
### Connecting phones in a P2P network
[![Xenikii Website](https://img.shields.io/badge/-xenikii.one-bd2727?style=flat&logoColor=white)](https://xenikii.one)
[![LICENSE BSD](https://img.shields.io/badge/License-BSD-4577d9)](https://github.com/ksenia312/nearby_service/blob/main/LICENSE)
+4 -5
View File
@@ -1,14 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xenikii.nearby_service">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- required to use Wi-fi Direct on Android 13+ -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<!-- required to use Wi-fi Direct -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- to check if Wi-fi is enabled -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- to work with Wi-fi Direct API -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-feature
android:name="android.hardware.wifi.direct"
@@ -1,15 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- to write files to storage after receiving them -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<application
android:label="nearby_service_example"
android:name="${applicationName}"
@@ -1,38 +1,28 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!-- to write files to storage after receiving them -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:label="nearby_service_example_full"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:label="nearby_service_example_full">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
+4
View File
@@ -22,12 +22,16 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>Permission to use the photo library is required to send the photos</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
+26 -4
View File
@@ -24,6 +24,8 @@ class AppService extends ChangeNotifier {
StreamSubscription? _connectedDeviceSubscription;
StreamSubscription? _connectionInfoSubscription;
final filesLoadings = <String, int>{};
@override
void dispose() {
stopListeningAll();
@@ -85,7 +87,6 @@ class AppService extends ChangeNotifier {
if (result ?? false) {
updateState(AppState.readyToDiscover);
startListeningConnectionInfo();
await getCurrentDeviceInfo();
return true;
}
return false;
@@ -102,6 +103,7 @@ class AppService extends ChangeNotifier {
Future<void> discover() async {
try {
await getCurrentDeviceInfo();
final result = await _nearbyService.discover();
if (result) {
updateState(AppState.discoveringPeers);
@@ -287,7 +289,11 @@ extension CommunicationChannelExtension on AppService {
onData: (event) async {
final files = await FilesSaver.savePack(event);
onFilesSaved?.call(
ReceivedNearbyFilesPack(sender: event.sender, files: files),
ReceivedNearbyFilesPack(
id: event.id,
sender: event.sender,
files: files,
),
);
},
);
@@ -349,16 +355,32 @@ extension MessagingExtension on AppService {
);
}
void sendFilesResponse(String requestId, {required bool isAccepted}) {
void sendFilesResponse(
NearbyMessageFilesRequest request, {
required bool isAccepted,
}) {
if (connectedDevice == null) return;
_nearbyService.send(
OutgoingNearbyMessage(
receiver: connectedDevice!.info,
content: NearbyMessageFilesResponse(
id: requestId,
id: request.id,
isAccepted: isAccepted,
),
),
);
if (isAccepted) {
setFilesLoading(request);
}
}
void setFilesLoading(NearbyMessageFilesRequest request) {
filesLoadings[request.id] = request.files.length;
_notify();
}
void endFilesLoading(ReceivedNearbyFilesPack pack) {
filesLoadings.remove(pack.id);
_notify();
}
}
+1
View File
@@ -8,6 +8,7 @@ Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final service = AppService();
await service.getPlatformInfo();
runApp(
App(service: service),
);
+20 -8
View File
@@ -12,20 +12,28 @@ const kWhiteColor = Color(0xFFFFFFFF);
const kGreyColor = Color(0xFF607D8B);
const kGreenColor = Color(0xFF07B988);
class App extends StatelessWidget {
class App extends StatefulWidget {
const App({super.key, required this.service});
final AppService service;
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
final scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: service,
value: widget.service,
child: MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: kBlueColor),
),
home: Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: const Text('Nearby service example app'),
actions: [
@@ -37,7 +45,7 @@ class App extends StatelessWidget {
}),
],
),
body: const _AppBody(),
body: _AppBody(scaffoldKey),
),
),
);
@@ -45,7 +53,9 @@ class App extends StatelessWidget {
}
class _AppBody extends StatelessWidget {
const _AppBody();
const _AppBody(this.scaffoldKey);
final GlobalKey<ScaffoldState> scaffoldKey;
@override
Widget build(BuildContext context) {
@@ -72,15 +82,17 @@ class _AppBody extends StatelessWidget {
final builder = AppStepViewBuilder(state: e);
final isActive = e == state;
return Step(
isActive: isActive,
title: builder.buildTitle(),
subtitle: builder.buildSubtitle(),
content: builder.buildContent(
scaffoldKey: scaffoldKey,
),
state: isActive
? StepState.indexed
: e.step < state.step
? StepState.complete
: StepState.disabled,
title: builder.buildTitle(),
subtitle: builder.buildSubtitle(),
content: builder.buildContent(),
isActive: isActive,
);
},
),
@@ -7,7 +7,9 @@ class AppStepViewBuilder {
final AppState state;
Widget buildContent() {
Widget buildContent({
required GlobalKey<ScaffoldState> scaffoldKey,
}) {
return switch (state) {
(AppState.idle) => const IdleView(),
(AppState.permissions) => const PermissionsView(),
@@ -19,7 +21,9 @@ class AppStepViewBuilder {
(AppState.loadingConnection) => const Center(
child: CircularProgressIndicator.adaptive(),
),
(AppState.connected) => const ConnectedView(),
(AppState.connected) => ConnectedView(
scaffoldKey: scaffoldKey,
),
(AppState.communicationChannelCreated) => const CommunicationView(),
};
}
@@ -0,0 +1,46 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:nearby_service_example_full/presentation/app.dart';
import 'package:nearby_service_example_full/utils/files_saver.dart';
class FilePreview extends StatelessWidget {
const FilePreview({super.key, required this.file});
final PlatformFile file;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: kGreenColor,
width: 2,
strokeAlign: 1,
),
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: FilesSaver.isImage(file.extension)
? Image.file(
File(file.path!),
fit: BoxFit.cover,
)
: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: Text(
file.name,
style: const TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: kGreenColor,
),
),
),
),
);
}
}
@@ -1,15 +1,21 @@
import 'package:flutter/material.dart';
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service_example_full/domain/app_service.dart';
import 'package:nearby_service_example_full/uikit/uikit.dart';
class FilesListener {
FilesListener._();
static void call(BuildContext context, ReceivedNearbyFilesPack pack) {
static void call(
BuildContext context, {
required AppService service,
required ReceivedNearbyFilesPack pack,
}) {
service.endFilesLoading(pack);
final senderSubtitle = 'From ${pack.sender.displayName} '
'(ID: ${pack.sender.id})';
AppShackBar.show(
Scaffold.of(context).context,
context,
'${pack.files.length} files saved! \n${pack.files.map((e) => e.name).join('\n')}',
subtitle: senderSubtitle,
);
@@ -2,28 +2,31 @@ import 'package:flutter/material.dart';
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service_example_full/domain/app_service.dart';
import 'package:nearby_service_example_full/uikit/uikit.dart';
import 'package:provider/provider.dart';
class MessagesListener {
MessagesListener._();
static void call(BuildContext context, ReceivedNearbyMessage message) {
static void call(
BuildContext context, {
required AppService service,
required ReceivedNearbyMessage message,
}) {
final senderSubtitle = 'From ${message.sender.displayName} '
'(ID: ${message.sender.id})';
message.content.byType(
onTextRequest: (textRequest) {
AppShackBar.show(
Scaffold.of(context).context,
context,
'${textRequest.value} (Message ID=${textRequest.id})',
subtitle: senderSubtitle,
)?.closed.whenComplete(() {
// mark as read
context.read<AppService>().sendTextResponse(textRequest.id);
service.sendTextResponse(textRequest.id);
});
},
onTextResponse: (textResponse) {
AppShackBar.show(
Scaffold.of(context).context,
context,
'Your message with ID=${textResponse.id} was delivered to ${message.sender.displayName}',
);
},
@@ -34,15 +37,15 @@ class MessagesListener {
subtitle: senderSubtitle,
).then(
// accept or dismiss the files request
(isAccepted) => context.read<AppService>().sendFilesResponse(
filesRequest.id,
isAccepted: isAccepted ?? false,
),
(isAccepted) => service.sendFilesResponse(
filesRequest,
isAccepted: isAccepted ?? false,
),
);
},
onFilesResponse: (filesResponse) {
AppShackBar.show(
Scaffold.of(context).context,
context,
filesResponse.isAccepted
? 'Request is accepted!'
: 'Request was denied :(',
@@ -1,12 +1,9 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service_example_full/domain/app_service.dart';
import 'package:nearby_service_example_full/presentation/app.dart';
import 'package:nearby_service_example_full/presentation/components/file_preview.dart';
import 'package:nearby_service_example_full/uikit/uikit.dart';
import 'package:nearby_service_example_full/utils/files_saver.dart';
import 'package:provider/provider.dart';
import '../components/device_preview.dart';
@@ -24,143 +21,126 @@ class _CommunicationViewState extends State<CommunicationView> {
@override
Widget build(BuildContext context) {
return Selector<AppService, NearbyDevice?>(
selector: (context, service) => service.connectedDevice,
builder: (context, device, _) {
if (device == null) {
return Center(
child: ActionButton(
onTap: context.read<AppService>().stopListeningAll,
title: 'Restart',
),
);
}
final inputBorder = OutlineInputBorder(
borderSide: const BorderSide(color: kGreenColor),
borderRadius: BorderRadius.circular(32),
);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DevicePreview(device: device, largeView: true),
const SizedBox(height: 10),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 2,
child: TextField(
onChanged: (value) => setState(() {
message = value;
}),
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
enabledBorder: inputBorder,
border: inputBorder,
focusedBorder: inputBorder,
hintStyle: const TextStyle(color: kGreenColor),
hintText: 'Enter a message',
final service = context.watch<AppService>();
final filesLoadings = service.filesLoadings;
final device = service.connectedDevice;
if (device == null) {
return Center(
child: ActionButton(
onTap: context.read<AppService>().stopListeningAll,
title: 'Restart',
),
);
}
final inputBorder = OutlineInputBorder(
borderSide: const BorderSide(color: kGreenColor),
borderRadius: BorderRadius.circular(32),
);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DevicePreview(device: device, largeView: true),
const SizedBox(height: 10),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 2,
child: TextField(
onChanged: (value) => setState(() {
message = value;
}),
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
enabledBorder: inputBorder,
border: inputBorder,
focusedBorder: inputBorder,
hintStyle: const TextStyle(color: kGreenColor),
hintText: 'Enter a message',
),
),
),
Flexible(
child: ActionButton(
title: 'Send',
onTap: () => context.read<AppService>().sendTextRequest(
message,
),
),
),
Flexible(
child: ActionButton(
title: 'Send',
onTap: () => context.read<AppService>().sendTextRequest(
message,
),
),
),
],
),
),
),
const SizedBox(height: 10),
const Text('OR'),
const SizedBox(height: 10),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 2,
child: ActionButton(
type: ActionType.success,
title: 'Choose files',
onTap: () async {
final result = await FilePicker.platform.pickFiles(
allowMultiple: true,
);
setState(() {
files = [...?result?.files];
});
},
),
),
Flexible(
child: ActionButton(
title: 'Send',
onTap: () => context.read<AppService>().sendFilesRequest([
...files
.map((e) => e.path)
.where((element) => element != null)
.cast<String>(),
]),
),
),
],
],
),
),
const SizedBox(height: 10),
const Text('OR'),
const SizedBox(height: 10),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 2,
child: ActionButton(
type: ActionType.success,
title: 'Choose files',
onTap: () async {
final result = await FilePicker.platform.pickFiles(
allowMultiple: true,
);
setState(() {
files = [...?result?.files];
});
},
),
),
),
const SizedBox(height: 10),
const Text('Selected files:', style: TextStyle(fontSize: 18)),
Flexible(
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
physics: const NeverScrollableScrollPhysics(),
children: [
...files.where((element) => element.path != null).map(
(e) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: kGreenColor),
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(11),
child: FilesSaver.isImage(e.extension)
? Image.file(
File(e.path!),
fit: BoxFit.cover,
)
: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: Text(
e.name,
style: const TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: kGreenColor,
),
),
),
),
);
},
),
],
Flexible(
child: ActionButton(
title: 'Send',
onTap: () => context.read<AppService>().sendFilesRequest([
...files
.map((e) => e.path)
.where((element) => element != null)
.cast<String>(),
]),
),
),
),
],
),
),
const SizedBox(height: 10),
Column(
children: [
...filesLoadings.entries.map((entry) {
return Text(
'Files pack ${entry.key} with ${entry.value} files is loading.',
);
}),
],
);
},
),
const SizedBox(height: 10),
const Text('Selected files:', style: TextStyle(fontSize: 18)),
Flexible(
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
physics: const NeverScrollableScrollPhysics(),
children: [
...files.where((element) => element.path != null).map(
(e) {
return FilePreview(file: e);
},
),
],
),
),
],
);
}
}
@@ -9,7 +9,9 @@ import 'package:provider/provider.dart';
import '../components/device_preview.dart';
class ConnectedView extends StatelessWidget {
const ConnectedView({super.key});
const ConnectedView({super.key, required this.scaffoldKey});
final GlobalKey<ScaffoldState> scaffoldKey;
@override
Widget build(BuildContext context) {
@@ -23,6 +25,8 @@ class ConnectedView extends StatelessWidget {
context.select<AppService, CommunicationChannelState>(
(service) => service.communicationChannelState,
);
final service = context.read<AppService>();
return device != null
? Center(
child: Column(
@@ -43,7 +47,7 @@ class ConnectedView extends StatelessWidget {
const Text('Connection lost'),
ActionButton(
onTap: () {
context.read<AppService>().connect(device);
service.connect(device);
},
title: 'Reconnect',
),
@@ -55,17 +59,24 @@ class ConnectedView extends StatelessWidget {
if (!communicationChannelState.isLoading)
ActionButton(
title: 'Start communicate',
onTap: () =>
context.read<AppService>().startCommunicationChannel(
listener: (event) => MessagesListener.call(
context,
event,
),
onFilesSaved: (files) => FilesListener.call(
context,
files,
),
),
onTap: () {
service.startCommunicationChannel(
listener: (event) {
MessagesListener.call(
scaffoldKey.currentState!.context,
service: service,
message: event,
);
},
onFilesSaved: (pack) {
FilesListener.call(
scaffoldKey.currentState!.context,
service: service,
pack: pack,
);
},
);
},
)
else
Text(
+7 -4
View File
@@ -11,24 +11,27 @@ import MultipeerConnectivity
class NearbyStartCommand {
init(senderName: String, filesCount: Int) {
init(id: String, senderName: String, filesCount: Int) {
self.id = id
self.senderName = senderName
self.filesCount = filesCount
}
static func fromUserInfo(userInfo: NearbyUserInfo)-> NearbyStartCommand? {
if let name = userInfo.dictionary["name"] as? String,
let filesCount = userInfo.dictionary["filesCount"] as? Int
let filesCount = userInfo.dictionary["filesCount"] as? Int,
let id = userInfo.dictionary["id"] as? String
{
return NearbyStartCommand(senderName: name, filesCount: filesCount)
return NearbyStartCommand(id: id, senderName: name, filesCount: filesCount)
}
return nil
}
func toDictionary() -> [String: Any] {
return ["name": senderName, "filesCount": filesCount]
return ["name": senderName, "filesCount": filesCount, "id": id]
}
let id: String
let senderName: String
let filesCount: Int
}
+1
View File
@@ -129,6 +129,7 @@ class NearbyManager: NSObject {
let device = NearbyDevicesStore.instance.find(for: receiverId)
if let requireDevice = device {
let command = NearbyStartCommand(
id: id,
senderName: self.device.name,
filesCount: paths.count
).toDictionary()
+5 -1
View File
@@ -15,9 +15,11 @@ class NearbyFilesStore {
private var senderName: String? = nil
private var maxCount: Int = 0
private var count: Int = 0
private var id: String? = nil
func startReceiving(command: NearbyStartCommand) {
self.paths.removeAll()
self.id = command.id
self.senderName = command.senderName
self.maxCount = command.filesCount
self.count = 0
@@ -33,6 +35,7 @@ class NearbyFilesStore {
}
func clear() {
self.id = nil
self.paths.removeAll()
self.senderName = nil
self.maxCount = 0
@@ -40,8 +43,9 @@ class NearbyFilesStore {
}
func toDartFormat(peerID: MCPeerID) -> String? {
if (senderName != nil) {
if (senderName != nil && id != nil) {
let object = [
"id": id!,
"files": paths.map { ["path": $0]},
"sender": ["id": peerID.displayName, "displayName": senderName!]
] as [String : Any]
+12 -3
View File
@@ -7,12 +7,14 @@ import 'package:nearby_service/nearby_service.dart';
///
class ReceivedNearbyFilesPack implements NearbyReceivedInterface {
const ReceivedNearbyFilesPack({
required this.id,
required this.sender,
required this.files,
});
factory ReceivedNearbyFilesPack.fromJson(Map<String, dynamic>? json) {
return ReceivedNearbyFilesPack(
id: json?['id'],
sender: NearbyDeviceInfo.fromJson(json?['sender']),
files: [
...?(json?['files'] as List?)?.map(
@@ -30,8 +32,14 @@ class ReceivedNearbyFilesPack implements NearbyReceivedInterface {
///
final List<NearbyFileInfo> files;
///
/// ID of the files pack
///
final String id;
Map<String, dynamic> toJson() {
return {
'id': id,
'sender': sender.toJson(),
'files': [
...files.map((e) => e.toJson()),
@@ -45,13 +53,14 @@ class ReceivedNearbyFilesPack implements NearbyReceivedInterface {
other is ReceivedNearbyFilesPack &&
runtimeType == other.runtimeType &&
sender == other.sender &&
files == other.files;
files == other.files &&
id == other.id;
@override
int get hashCode => sender.hashCode ^ files.hashCode;
int get hashCode => sender.hashCode ^ files.hashCode ^ id.hashCode;
@override
String toString() {
return 'NearbyFilesPack{sender: $sender, files: $files}';
return 'ReceivedNearbyFilesPack{sender: $sender, files: $files, id: $id}';
}
}
+1 -1
View File
@@ -177,6 +177,6 @@ final class NearbyMessageFilesResponse extends NearbyMessageContent {
@override
String toString() {
return 'NearbyMessageFileResponse{response: $isAccepted, id: $id}';
return 'NearbyMessageFileResponse{isAccepted: $isAccepted, id: $id}';
}
}
+3
View File
@@ -63,13 +63,16 @@ class FilesSocket {
} else if (event == separateCommandOf(_currentFileIndex)) {
_futures.add(_createFile(_currentFileIndex));
_currentFileIndex = _currentFileIndex + 1;
_chunksCount = 0;
_bytesTable['$_currentFileIndex'] = [];
Logger.info('Completed receiving file №${_currentFileIndex - 1}');
} else if (event == finishCommand) {
await Future.wait(_futures);
Logger.info('Files pack ${filesRequest.id} was created');
listener?.onData.call(
ReceivedNearbyFilesPack(
id: filesRequest.id,
sender: sender,
files: _files,
),