328 lines
9.0 KiB
Dart
328 lines
9.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:nearby_service/nearby_service.dart';
|
|
|
|
/// All UI components and utilities are moved out of main
|
|
/// to focus on the nearby_service plugin.
|
|
/// If you are interested to see them,
|
|
/// see https://github.com/ksenia312/nearby_service/blob/main/example/lib/.
|
|
import 'components/connected_device_view.dart';
|
|
import 'components/files_messaging_view.dart';
|
|
import 'components/ios_role_selector.dart';
|
|
import 'components/peer_widget.dart';
|
|
import 'components/text_messaging_view.dart';
|
|
import 'utils/app_snack_bar.dart';
|
|
import 'utils/file_saver.dart';
|
|
|
|
/// Short example variant for pub.dev
|
|
/// See the full example at
|
|
/// https://github.com/ksenia312/nearby_service/tree/main/example_full
|
|
Future<void> main() async {
|
|
runApp(const App());
|
|
}
|
|
|
|
/// Simplified application states, main 3:
|
|
/// - Preparatory
|
|
/// - Device search
|
|
/// - Communicating with the connected device
|
|
enum AppState { idle, discovering, connected }
|
|
|
|
class App extends StatelessWidget {
|
|
const App({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
title: 'Nearby Service Example',
|
|
home: Scaffold(
|
|
appBar: AppBar(title: const Text('Nearby Service Example')),
|
|
body: Container(
|
|
alignment: Alignment.topCenter,
|
|
padding: const EdgeInsets.all(20),
|
|
child: const AppBody(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class AppBody extends StatefulWidget {
|
|
const AppBody({super.key});
|
|
|
|
@override
|
|
State<AppBody> createState() => _AppBodyState();
|
|
}
|
|
|
|
class _AppBodyState extends State<AppBody> {
|
|
/// Our service
|
|
final _nearbyService = NearbyService.getInstance(
|
|
/// Define log level here
|
|
logLevel: NearbyServiceLogLevel.debug,
|
|
);
|
|
AppState _state = AppState.idle;
|
|
|
|
/// Browser OR Advertiser for IOS
|
|
bool _isIosBrowser = true;
|
|
|
|
/// List of discovered devices
|
|
List<NearbyDevice> _peers = [];
|
|
StreamSubscription? _peersSubscription;
|
|
|
|
/// Temporary solution to check the connection,
|
|
/// use [NearbyService.getConnectedDeviceStream] for this purpose
|
|
/// in your application
|
|
Timer? _connectionCheckTimer;
|
|
NearbyDevice? _connectedDevice;
|
|
|
|
@override
|
|
void initState() {
|
|
_initialize();
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_peersSubscription?.cancel();
|
|
_connectionCheckTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_state == AppState.idle) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (Platform.isIOS)
|
|
IOSRoleSelector(
|
|
isIosBrowser: _isIosBrowser,
|
|
onSelect: (value) => setState(() => _isIosBrowser = value),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: _startProcess,
|
|
child: const Text('Start'),
|
|
),
|
|
],
|
|
);
|
|
} else if (_state == AppState.discovering) {
|
|
return ListView(
|
|
children: [
|
|
if (Platform.isIOS)
|
|
Text('You are ${_isIosBrowser ? 'Browser' : 'Advertiser'}'),
|
|
..._peers.map(
|
|
(e) => PeerWidget(
|
|
device: e,
|
|
isIosBrowser: _isIosBrowser,
|
|
onConnect: _connect,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
} else if (_state == AppState.connected) {
|
|
return SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Preview of the connected device
|
|
ConnectedDeviceView(
|
|
device: _connectedDevice!,
|
|
onDisconnect: _disconnect,
|
|
),
|
|
const SizedBox(height: 50),
|
|
// Send messages section
|
|
TextMessagingView(
|
|
onSend: (message) => _send(
|
|
NearbyMessageTextRequest.create(value: message),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 50),
|
|
// Send files section
|
|
FilesMessagingView(
|
|
onSend: (files) {
|
|
_send(
|
|
NearbyMessageFilesRequest.create(files: files),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
return Container();
|
|
}
|
|
|
|
Future<void> _initialize() async {
|
|
await _nearbyService.initialize();
|
|
}
|
|
|
|
Future<void> _startProcess() async {
|
|
final platformsReady = await _checkPlatforms();
|
|
if (platformsReady) {
|
|
await _discover();
|
|
}
|
|
}
|
|
|
|
Future<bool> _checkPlatforms() async {
|
|
if (Platform.isAndroid) {
|
|
final isGranted = await _nearbyService.android?.requestPermissions();
|
|
final wifiEnabled = await _nearbyService.android?.checkWifiService();
|
|
return (isGranted ?? false) && (wifiEnabled ?? false);
|
|
} else if (Platform.isIOS) {
|
|
_nearbyService.ios?.setIsBrowser(value: _isIosBrowser);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> _discover() async {
|
|
final result = await _nearbyService.discover();
|
|
if (result) {
|
|
setState(() {
|
|
_state = AppState.discovering;
|
|
});
|
|
_peersSubscription = _nearbyService.getPeersStream().listen((event) {
|
|
setState(() {
|
|
_peers = event;
|
|
// Check and filter your peers here
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _connect(NearbyDevice device) async {
|
|
// Be careful with already connected devices,
|
|
// double connection may be unnecessary
|
|
final result = await _nearbyService.connect(device);
|
|
if (result || device.status.isConnected) {
|
|
_connectionCheckTimer = Timer.periodic(
|
|
const Duration(seconds: 3),
|
|
(_) {
|
|
NearbyDevice? selectedDevice;
|
|
try {
|
|
selectedDevice = _peers.firstWhere(
|
|
(element) => element.info.id == device.info.id,
|
|
);
|
|
} finally {}
|
|
|
|
if (selectedDevice.status.isConnected) {
|
|
try {
|
|
_startCommunicationChannel(device);
|
|
} finally {
|
|
_connectionCheckTimer?.cancel();
|
|
_connectionCheckTimer = null;
|
|
}
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
void _startCommunicationChannel(NearbyDevice device) {
|
|
_nearbyService.startCommunicationChannel(
|
|
NearbyCommunicationChannelData(
|
|
device.info.id,
|
|
filesListener: NearbyServiceFilesListener(onData: _filesListener),
|
|
messagesListener: NearbyServiceMessagesListener(
|
|
onData: _messagesListener,
|
|
onCreated: () {
|
|
setState(() {
|
|
_connectedDevice = device;
|
|
_state = AppState.connected;
|
|
});
|
|
},
|
|
onError: (e, [StackTrace? s]) {
|
|
setState(() {
|
|
_connectedDevice = null;
|
|
_state = AppState.idle;
|
|
});
|
|
},
|
|
onDone: () {
|
|
setState(() {
|
|
_connectedDevice = null;
|
|
_state = AppState.idle;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _disconnect() async {
|
|
try {
|
|
await _nearbyService.disconnect(_connectedDevice!);
|
|
} finally {
|
|
await _nearbyService.endCommunicationChannel();
|
|
await _nearbyService.stopDiscovery();
|
|
await _peersSubscription?.cancel();
|
|
setState(() {
|
|
_state = AppState.idle;
|
|
});
|
|
}
|
|
}
|
|
|
|
void _messagesListener(ReceivedNearbyMessage<NearbyMessageContent> message) {
|
|
if (_connectedDevice == null) return;
|
|
// Very useful stuff! Process messages according to the type of content
|
|
message.content.byType(
|
|
onTextRequest: (request) {
|
|
AppSnackBar.show(
|
|
context,
|
|
title: request.value,
|
|
subtitle: message.sender.displayName,
|
|
).whenComplete(
|
|
() => _send(NearbyMessageTextResponse(id: request.id)),
|
|
);
|
|
},
|
|
onTextResponse: (response) {
|
|
AppSnackBar.show(
|
|
context,
|
|
title: 'Message ${response.id} was delivered',
|
|
subtitle: message.sender.displayName,
|
|
);
|
|
},
|
|
onFilesRequest: (request) {
|
|
AppSnackBar.show(
|
|
context,
|
|
title: 'Request to receive ${request.files.length} files',
|
|
subtitle: message.sender.displayName,
|
|
actionName: 'Accept the files?',
|
|
onAcceptAction: () {
|
|
_send(
|
|
NearbyMessageFilesResponse(id: request.id, isAccepted: true),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
onFilesResponse: (response) {
|
|
AppSnackBar.show(
|
|
context,
|
|
title: 'Response ${response.id} for the files '
|
|
'${response.isAccepted ? 'is accepted' : 'was denied'}',
|
|
subtitle: message.sender.displayName,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
void _filesListener(ReceivedNearbyFilesPack pack) {
|
|
FilesSaver.savePack(pack).then(
|
|
(value) => AppSnackBar.show(context, title: 'Files pack was saved'),
|
|
);
|
|
}
|
|
|
|
Future<void> _send(NearbyMessageContent content) async {
|
|
if (_connectedDevice == null) return;
|
|
await _nearbyService.send(
|
|
OutgoingNearbyMessage(
|
|
content: content,
|
|
receiver: _connectedDevice!.info,
|
|
),
|
|
);
|
|
}
|
|
}
|