abe519dae7
* Add macOS support * Update README * Update to v0.2.0 * Document breaking changes * Delete widget tests
450 lines
11 KiB
Dart
450 lines
11 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:nearby_service/nearby_service.dart';
|
|
import 'package:nearby_service_example_full/utils/files_saver.dart';
|
|
|
|
import 'app_state.dart';
|
|
|
|
class AppService extends ChangeNotifier {
|
|
late final _nearbyService = NearbyService.getInstance();
|
|
|
|
AppState state = AppState.idle;
|
|
List<NearbyDevice>? peers;
|
|
NearbyDevice? connectedDevice;
|
|
NearbyDeviceInfo? currentDeviceInfo;
|
|
NearbyConnectionAndroidInfo? _connectionAndroidInfo;
|
|
CommunicationChannelState _communicationChannelState =
|
|
CommunicationChannelState.notConnected;
|
|
bool _isDarwinBrowser = true;
|
|
|
|
String platformVersion = 'Unknown';
|
|
String platformModel = 'Unknown';
|
|
|
|
StreamSubscription? _peersSubscription;
|
|
StreamSubscription? _connectedDeviceSubscription;
|
|
StreamSubscription? _connectionInfoSubscription;
|
|
|
|
final filesLoadings = <String, int>{};
|
|
|
|
@override
|
|
void dispose() {
|
|
stopListeningAll();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> getPlatformInfo() async {
|
|
platformVersion = await _nearbyService.getPlatformVersion() ?? 'Unknown';
|
|
platformModel = await _nearbyService.getPlatformModel() ?? 'Unknown';
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<String> getSavedDarwinDeviceName() async {
|
|
return (await _nearbyService.darwin?.getSavedDeviceName()) ?? platformModel;
|
|
}
|
|
|
|
Future<void> initialize(String? darwinDeviceName) async {
|
|
try {
|
|
await _nearbyService.initialize(
|
|
data: NearbyInitializeData(darwinDeviceName: darwinDeviceName),
|
|
);
|
|
_nearbyService.darwin?.getIsBrowserStream().listen((event) {
|
|
_isDarwinBrowser = event;
|
|
});
|
|
startListeningCommunicationChannelState();
|
|
updateState(
|
|
Platform.isAndroid ? AppState.permissions : AppState.selectClientType,
|
|
);
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
} finally {
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> getCurrentDeviceInfo() async {
|
|
try {
|
|
currentDeviceInfo = await _nearbyService.getCurrentDeviceInfo();
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
}
|
|
|
|
Future<void> requestPermissions() async {
|
|
try {
|
|
final result = await _nearbyService.android?.requestPermissions();
|
|
if (result ?? false) {
|
|
updateState(AppState.checkServices);
|
|
}
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
}
|
|
|
|
Future<bool> checkWifiService() async {
|
|
final result = await _nearbyService.android?.checkWifiService();
|
|
if (result ?? false) {
|
|
updateState(AppState.readyToDiscover);
|
|
startListeningConnectionInfo();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Future<void> openServicesSettings() async {
|
|
await _nearbyService.openServicesSettings();
|
|
}
|
|
|
|
void setIsBrowser({required bool value}) {
|
|
_nearbyService.darwin?.setIsBrowser(value: value);
|
|
updateState(AppState.readyToDiscover);
|
|
}
|
|
|
|
Future<bool> hasRunningJobs() async {
|
|
try {
|
|
final result = await _nearbyService.getPeers();
|
|
// if one of devices is connecting, service is busy (android only)
|
|
if (result.any(
|
|
(element) => element.status == NearbyDeviceStatus.connecting,
|
|
)) {
|
|
if (kDebugMode) {
|
|
print('Service has already running jobs');
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> discover() async {
|
|
try {
|
|
await getCurrentDeviceInfo();
|
|
final hasRunning = await hasRunningJobs();
|
|
if (hasRunning) {
|
|
updateState(AppState.discoveringPeers);
|
|
} else {
|
|
final result = await _nearbyService.discover();
|
|
if (result) {
|
|
updateState(AppState.discoveringPeers);
|
|
}
|
|
}
|
|
} on NearbyServiceBusyException catch (_) {
|
|
_logBusyException();
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
}
|
|
|
|
Future<void> stopDiscovery() async {
|
|
try {
|
|
final hasRunning = await hasRunningJobs();
|
|
if (hasRunning) {
|
|
await cancelConnect();
|
|
}
|
|
final result = await _nearbyService.stopDiscovery();
|
|
if (result) {
|
|
updateState(AppState.readyToDiscover);
|
|
}
|
|
} on NearbyServiceBusyException catch (_) {
|
|
_logBusyException();
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
}
|
|
|
|
Future<void> connect(NearbyDevice device) async {
|
|
try {
|
|
await _nearbyService.connectById(device.info.id);
|
|
} on NearbyServiceBusyException catch (_) {
|
|
_logBusyException();
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> disconnect([NearbyDevice? device]) async {
|
|
try {
|
|
await _nearbyService.disconnectById(device?.info.id);
|
|
} on NearbyServiceBusyException catch (_) {
|
|
_logBusyException();
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
} finally {
|
|
await stopListeningAll();
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> cancelConnect() async {
|
|
try {
|
|
await _nearbyService.android?.cancelLastConnectionProcess();
|
|
} on NearbyServiceBusyException catch (_) {
|
|
_logBusyException();
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> stopListeningAll() async {
|
|
await endCommunicationChannel();
|
|
await stopListeningConnectedDevice();
|
|
await stopListeningPeers();
|
|
await stopListeningConnectionInfo();
|
|
await stopDiscovery();
|
|
}
|
|
|
|
void updateState(AppState state, {bool shouldNotify = true}) {
|
|
this.state = state;
|
|
if (shouldNotify) {
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void _notify() => notifyListeners();
|
|
}
|
|
|
|
extension GettersExtension on AppService {
|
|
CommunicationChannelState get communicationChannelState =>
|
|
_communicationChannelState;
|
|
|
|
bool get isDarwinBrowser {
|
|
return _isDarwinBrowser;
|
|
}
|
|
|
|
bool? get isAndroidGroupOwner {
|
|
return _connectionAndroidInfo?.isGroupOwner;
|
|
}
|
|
}
|
|
|
|
extension ConnectionInfoExtension on AppService {
|
|
void startListeningConnectionInfo() {
|
|
try {
|
|
_connectionInfoSubscription =
|
|
_nearbyService.android?.getConnectionInfoStream().listen(
|
|
(event) async {
|
|
_connectionAndroidInfo = event;
|
|
_notify();
|
|
},
|
|
);
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
_notify();
|
|
}
|
|
|
|
void startListeningCommunicationChannelState() {
|
|
try {
|
|
_connectionInfoSubscription =
|
|
_nearbyService.getCommunicationChannelStateStream().listen(
|
|
(event) async {
|
|
_communicationChannelState = event;
|
|
_notify();
|
|
},
|
|
);
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
_notify();
|
|
}
|
|
|
|
Future<void> stopListeningConnectionInfo() async {
|
|
await _connectionInfoSubscription?.cancel();
|
|
_connectionInfoSubscription = null;
|
|
}
|
|
}
|
|
|
|
extension PeersExtension on AppService {
|
|
Future<void> startListeningPeers() async {
|
|
try {
|
|
_peersSubscription = _nearbyService.getPeersStream().listen(
|
|
(event) {
|
|
peers = event;
|
|
_notify();
|
|
},
|
|
);
|
|
updateState(AppState.streamingPeers);
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
}
|
|
|
|
Future<void> stopListeningPeers() async {
|
|
await _peersSubscription?.cancel();
|
|
peers = null;
|
|
updateState(AppState.discoveringPeers);
|
|
}
|
|
}
|
|
|
|
extension ConnectedDeviceExtension on AppService {
|
|
Future<void> startListeningConnectedDevice(NearbyDevice device) async {
|
|
updateState(AppState.loadingConnection);
|
|
try {
|
|
_connectedDeviceSubscription =
|
|
_nearbyService.getConnectedDeviceStreamById(device.info.id).listen(
|
|
(event) async {
|
|
final wasConnected = connectedDevice?.status.isConnected ?? false;
|
|
final nowConnected = event?.status.isConnected ?? false;
|
|
if (wasConnected && !nowConnected) {
|
|
stopListeningAll();
|
|
return;
|
|
}
|
|
connectedDevice = event;
|
|
if (connectedDevice != null &&
|
|
state != AppState.connected &&
|
|
state != AppState.communicationChannelCreated) {
|
|
updateState(AppState.connected);
|
|
}
|
|
_notify();
|
|
},
|
|
);
|
|
} catch (e) {
|
|
updateState(AppState.streamingPeers, shouldNotify: false);
|
|
}
|
|
_notify();
|
|
}
|
|
|
|
Future<void> stopListeningConnectedDevice() async {
|
|
await _connectedDeviceSubscription?.cancel();
|
|
await _nearbyService.endCommunicationChannel();
|
|
_connectedDeviceSubscription = null;
|
|
connectedDevice = null;
|
|
_notify();
|
|
}
|
|
}
|
|
|
|
extension CommunicationChannelExtension on AppService {
|
|
Future<void> startCommunicationChannel({
|
|
ValueChanged<ReceivedNearbyMessage>? listener,
|
|
ValueChanged<ReceivedNearbyFilesPack>? onFilesSaved,
|
|
}) async {
|
|
final messagesListener = NearbyServiceMessagesListener(
|
|
onCreated: () {
|
|
updateState(AppState.communicationChannelCreated);
|
|
},
|
|
onData: (event) {
|
|
listener?.call(event);
|
|
},
|
|
onError: (e, [StackTrace? s]) {
|
|
stopListeningAll();
|
|
},
|
|
);
|
|
final filesListener = NearbyServiceFilesListener(
|
|
onData: (event) async {
|
|
final files = await FilesSaver.savePack(event);
|
|
onFilesSaved?.call(
|
|
ReceivedNearbyFilesPack(
|
|
id: event.id,
|
|
sender: event.sender,
|
|
files: files,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
await _nearbyService.startCommunicationChannel(
|
|
NearbyCommunicationChannelData(
|
|
connectedDevice!.info.id,
|
|
messagesListener: messagesListener,
|
|
filesListener: filesListener,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> endCommunicationChannel() async {
|
|
try {
|
|
await _nearbyService.endCommunicationChannel();
|
|
} catch (e, s) {
|
|
_log(e, s);
|
|
}
|
|
_notify();
|
|
}
|
|
}
|
|
|
|
extension MessagingExtension on AppService {
|
|
void sendTextRequest(String message) {
|
|
if (connectedDevice == null) return;
|
|
_nearbyService.send(
|
|
OutgoingNearbyMessage(
|
|
content: NearbyMessageTextRequest.create(value: message),
|
|
receiver: connectedDevice!.info,
|
|
),
|
|
);
|
|
}
|
|
|
|
void sendFilesRequest(List<String> paths) {
|
|
if (connectedDevice == null) return;
|
|
_nearbyService.send(
|
|
OutgoingNearbyMessage(
|
|
content: NearbyMessageFilesRequest.create(
|
|
files: [
|
|
...paths.map((e) => NearbyFileInfo(path: e)),
|
|
],
|
|
),
|
|
receiver: connectedDevice!.info,
|
|
),
|
|
);
|
|
}
|
|
|
|
void sendTextResponse(String requestId) {
|
|
if (connectedDevice == null) return;
|
|
|
|
_nearbyService.send(
|
|
OutgoingNearbyMessage(
|
|
receiver: connectedDevice!.info,
|
|
content: NearbyMessageTextResponse(id: requestId),
|
|
),
|
|
);
|
|
}
|
|
|
|
void sendFilesResponse(
|
|
NearbyMessageFilesRequest request, {
|
|
required bool isAccepted,
|
|
}) {
|
|
if (connectedDevice == null) return;
|
|
_nearbyService.send(
|
|
OutgoingNearbyMessage(
|
|
receiver: connectedDevice!.info,
|
|
content: NearbyMessageFilesResponse(
|
|
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();
|
|
}
|
|
}
|
|
|
|
extension LoggingExtension on AppService {
|
|
void _log(e, StackTrace s) {
|
|
if (kDebugMode) {
|
|
print('$e, \nStacktrace: $s');
|
|
}
|
|
}
|
|
|
|
void _logBusyException() {
|
|
if (kDebugMode) {
|
|
print(
|
|
'Nearby service is busy, wait a little and retry (You can implement retry in your code)',
|
|
);
|
|
}
|
|
}
|
|
}
|