Files
nearby_service/example_full/lib/domain/app_service.dart
T
Ben Hagen abe519dae7 Add macOS support (#23)
* Add macOS support

* Update README

* Update to v0.2.0

* Document breaking changes

* Delete widget tests
2025-05-13 16:24:58 +02:00

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)',
);
}
}
}