From c608b6c7df9d62d2ad7d7f4e6d3820ed44975f88 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Fri, 30 Jan 2026 00:09:13 +0900 Subject: [PATCH] =?UTF-8?q?feat(bluetooth):=20=EC=8B=A4=EC=A0=9C=20BLE=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EB=A7=9B=EC=A7=91=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B3=B5=EC=9C=A0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ble_peripheral 패키지 추가 (Peripheral/GATT Server 역할) - flutter_blue_plus 패키지 활용 (Central/스캔/연결 역할) 변경 사항: - BleConstants: BLE UUID 및 설정 상수 정의 - BluetoothService: Mock에서 실제 BLE 통신으로 전면 재작성 - Receiver: GATT Server로 광고, Write callback으로 데이터 수신 - Sender: 서비스 UUID로 스캔, 연결 후 Characteristic에 데이터 전송 - 청크 기반 대용량 데이터 전송 프로토콜 구현 - ShareDevice: BLE 디바이스 정보 필드 추가 (remoteId, rssi, bleDevice) - Android/iOS: BLE 권한 및 기능 선언 추가 데이터 전송 프로토콜: - 'CHUNK:index:total:data' 형식으로 500바이트 단위 분할 전송 - 수신 측에서 청크 조립 후 JSON 파싱 --- android/app/src/main/AndroidManifest.xml | 3 + ios/Runner/Info.plist | 4 + lib/core/constants/ble_constants.dart | 32 ++ lib/core/services/bluetooth_service.dart | 477 +++++++++++++++--- lib/domain/entities/share_device.dart | 57 +++ .../pages/share/share_screen.dart | 6 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 8 + pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 11 files changed, 534 insertions(+), 60 deletions(-) create mode 100644 lib/core/constants/ble_constants.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 747ff55..ab6f0ab 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,7 @@ + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9bc43a9..3b54e38 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -54,6 +54,10 @@ 맛집과의 거리 계산을 위해 위치 정보가 필요합니다. NSLocationAlwaysAndWhenInUseUsageDescription 맛집과의 거리 계산을 위해 위치 정보가 필요합니다. + NSBluetoothAlwaysUsageDescription + 맛집 리스트를 다른 사용자와 공유하기 위해 블루투스 권한이 필요합니다. + NSBluetoothPeripheralUsageDescription + 맛집 리스트를 다른 사용자로부터 받기 위해 블루투스 권한이 필요합니다. GADApplicationIdentifier $(GAD_APPLICATION_ID) diff --git a/lib/core/constants/ble_constants.dart b/lib/core/constants/ble_constants.dart new file mode 100644 index 0000000..aa27f84 --- /dev/null +++ b/lib/core/constants/ble_constants.dart @@ -0,0 +1,32 @@ +/// BLE 공유 기능에서 사용하는 상수 정의 +class BleConstants { + BleConstants._(); + + /// LunchPick 전용 서비스 UUID + static const String serviceUuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'; + + /// 맛집 데이터 전송용 Characteristic UUID + static const String dataCharacteristicUuid = + 'a1b2c3d4-e5f6-7890-abcd-ef1234567891'; + + /// 공유 코드 확인용 Characteristic UUID (매칭용) + static const String codeCharacteristicUuid = + 'a1b2c3d4-e5f6-7890-abcd-ef1234567892'; + + /// 청크 사이즈 (MTU - 3, 보수적으로 설정) + /// BLE 표준 MTU는 23바이트지만, 협상을 통해 더 커질 수 있음 + /// 안전하게 500바이트로 설정 + static const int chunkSize = 500; + + /// 광고 이름 prefix + static const String advertiseNamePrefix = 'LP-'; + + /// 스캔 타임아웃 (초) + static const int scanTimeoutSeconds = 10; + + /// 연결 타임아웃 (초) + static const int connectionTimeoutSeconds = 15; + + /// 청크 전송 간 딜레이 (밀리초) + static const int chunkDelayMs = 50; +} diff --git a/lib/core/services/bluetooth_service.dart b/lib/core/services/bluetooth_service.dart index 0c163c1..f0f3d8a 100644 --- a/lib/core/services/bluetooth_service.dart +++ b/lib/core/services/bluetooth_service.dart @@ -1,89 +1,452 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:math'; +import 'dart:io'; +import 'package:ble_peripheral/ble_peripheral.dart' as ble_peripheral; +import 'package:flutter/foundation.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:lunchpick/core/constants/ble_constants.dart'; import 'package:lunchpick/domain/entities/restaurant.dart'; import 'package:lunchpick/domain/entities/share_device.dart'; -/// 실제 Bluetooth 통신을 대체하는 간단한 모의(Mock) 서비스. +/// BLE 기반 맛집 리스트 공유 서비스 +/// +/// - Receiver (공유받기): ble_peripheral 패키지로 GATT Server 역할 +/// - Sender (공유하기): flutter_blue_plus 패키지로 Central 역할 class BluetoothService { + // === Peripheral (Receiver) 관련 === + bool _isPeripheralInitialized = false; + String? _currentShareCode; final _incomingDataController = StreamController.broadcast(); - final Map _listeningDevices = {}; - final Random _random = Random(); + final _receivedChunks = {}; + // ignore: unused_field - 디버깅 및 진행률 표시용 + int _expectedTotalChunks = 0; + // === Central (Sender) 관련 === + final List _discoveredDevices = []; + StreamSubscription>? _scanSubscription; + BluetoothDevice? _connectedDevice; + + // === 진행 상태 콜백 === + Function(int current, int total)? onSendProgress; + Function(int current, int total)? onReceiveProgress; + + /// 수신된 데이터 스트림 Stream get onDataReceived => _incomingDataController.stream; - /// 특정 코드로 수신 대기를 시작한다. - Future startListening(String code) async { - await Future.delayed(const Duration(milliseconds: 300)); - stopListening(); - final shareDevice = ShareDevice( - code: code, - deviceId: 'LP-${_random.nextInt(900000) + 100000}', - discoveredAt: DateTime.now(), - ); - _listeningDevices[code] = shareDevice; - } + /// 현재 광고 중인 공유 코드 + String? get currentShareCode => _currentShareCode; - /// 더 이상 수신 대기하지 않는다. - void stopListening() { - if (_listeningDevices.isEmpty) return; - final codes = List.from(_listeningDevices.keys); - for (final code in codes) { - _listeningDevices.remove(code); + /// BLE 지원 여부 확인 + Future get isSupported async { + if (!Platform.isAndroid && !Platform.isIOS) { + return false; + } + try { + return await FlutterBluePlus.isSupported; + } catch (_) { + return false; } } - /// 현재 주변에서 수신 대기 중인 기기 목록을 반환한다. - Future> scanNearbyDevices() async { - await Future.delayed(const Duration(seconds: 1)); - return _listeningDevices.values.toList(); + /// Bluetooth 어댑터 상태 확인 + Future get isBluetoothOn async { + try { + final state = await FlutterBluePlus.adapterState.first; + return state == BluetoothAdapterState.on; + } catch (_) { + return false; + } } - /// 대상 코드로 맛집 리스트를 전송한다. 실제 BT 대신 JSON 문자열을 브로드캐스트한다. + // ========================================================================= + // Receiver (Peripheral) 메서드 - ble_peripheral 사용 + // ========================================================================= + + /// Peripheral 초기화 + Future initializePeripheral() async { + if (_isPeripheralInitialized) return; + if (!Platform.isAndroid && !Platform.isIOS) { + throw UnsupportedError('BLE Peripheral은 Android/iOS에서만 지원됩니다.'); + } + + try { + await ble_peripheral.BlePeripheral.initialize(); + _setupWriteCallback(); + _isPeripheralInitialized = true; + debugPrint('[BLE] Peripheral 초기화 완료'); + } catch (e) { + debugPrint('[BLE] Peripheral 초기화 실패: $e'); + rethrow; + } + } + + /// 공유 코드로 수신 대기 시작 + Future startListening(String code) async { + await initializePeripheral(); + + // 기존 서비스 정리 + await _cleanupPeripheral(); + + _currentShareCode = code; + _receivedChunks.clear(); + _expectedTotalChunks = 0; + + try { + // GATT 서비스 추가 + await ble_peripheral.BlePeripheral.addService( + ble_peripheral.BleService( + uuid: BleConstants.serviceUuid, + primary: true, + characteristics: [ + // 데이터 수신용 Characteristic (writable) + ble_peripheral.BleCharacteristic( + uuid: BleConstants.dataCharacteristicUuid, + properties: [ + ble_peripheral.CharacteristicProperties.write.index, + ble_peripheral.CharacteristicProperties.writeWithoutResponse.index, + ], + permissions: [ble_peripheral.AttributePermissions.writeable.index], + value: null, + ), + // 공유 코드 확인용 Characteristic (readable) + ble_peripheral.BleCharacteristic( + uuid: BleConstants.codeCharacteristicUuid, + properties: [ble_peripheral.CharacteristicProperties.read.index], + permissions: [ble_peripheral.AttributePermissions.readable.index], + value: Uint8List.fromList(utf8.encode(code)), + ), + ], + ), + ); + + // 광고 시작 + await ble_peripheral.BlePeripheral.startAdvertising( + services: [BleConstants.serviceUuid], + localName: '${BleConstants.advertiseNamePrefix}$code', + ); + + debugPrint('[BLE] 광고 시작: ${BleConstants.advertiseNamePrefix}$code'); + } catch (e) { + debugPrint('[BLE] 광고 시작 실패: $e'); + _currentShareCode = null; + rethrow; + } + } + + /// Write 콜백 설정 + void _setupWriteCallback() { + ble_peripheral.BlePeripheral.setWriteRequestCallback(( + deviceId, + characteristicId, + offset, + value, + ) { + if (characteristicId.toLowerCase() == + BleConstants.dataCharacteristicUuid.toLowerCase()) { + _handleReceivedChunk(value ?? Uint8List(0)); + } + // null 반환 = 성공 (ble_peripheral 패키지 규약) + return null; + }); + + debugPrint('[BLE] Write 콜백 설정 완료'); + } + + /// 수신된 청크 처리 + void _handleReceivedChunk(Uint8List data) { + try { + final decoded = utf8.decode(data); + debugPrint('[BLE] 청크 수신: ${decoded.substring(0, decoded.length.clamp(0, 50))}...'); + + // 프로토콜: "CHUNK:index:total:data" + if (decoded.startsWith('CHUNK:')) { + final colonIndex1 = decoded.indexOf(':', 6); + final colonIndex2 = decoded.indexOf(':', colonIndex1 + 1); + + if (colonIndex1 == -1 || colonIndex2 == -1) { + debugPrint('[BLE] 잘못된 청크 형식'); + return; + } + + final index = int.tryParse(decoded.substring(6, colonIndex1)); + final total = int.tryParse(decoded.substring(colonIndex1 + 1, colonIndex2)); + final chunkData = decoded.substring(colonIndex2 + 1); + + if (index == null || total == null) { + debugPrint('[BLE] 청크 인덱스/전체 파싱 실패'); + return; + } + + _expectedTotalChunks = total; + _receivedChunks[index] = chunkData; + + // 진행 상태 콜백 + onReceiveProgress?.call(_receivedChunks.length, total); + + debugPrint('[BLE] 청크 $index/$total 수신 완료'); + + // 모든 청크 수신 완료 + if (_receivedChunks.length == total) { + _assembleAndEmit(); + } + } + } catch (e) { + debugPrint('[BLE] 청크 처리 오류: $e'); + } + } + + /// 청크 조립 후 스트림으로 전송 + void _assembleAndEmit() { + final sorted = _receivedChunks.entries.toList() + ..sort((a, b) => a.key.compareTo(b.key)); + final fullData = sorted.map((e) => e.value).join(); + + debugPrint('[BLE] 데이터 조립 완료: ${fullData.length} bytes'); + + _incomingDataController.add(fullData); + _receivedChunks.clear(); + _expectedTotalChunks = 0; + } + + /// Peripheral 정리 + Future _cleanupPeripheral() async { + try { + await ble_peripheral.BlePeripheral.stopAdvertising(); + await ble_peripheral.BlePeripheral.clearServices(); + } catch (e) { + debugPrint('[BLE] Peripheral 정리 중 오류 (무시됨): $e'); + } + } + + /// 수신 대기 중지 + Future stopListening() async { + await _cleanupPeripheral(); + _currentShareCode = null; + _receivedChunks.clear(); + _expectedTotalChunks = 0; + debugPrint('[BLE] 광고 중지'); + } + + // ========================================================================= + // Sender (Central) 메서드 - flutter_blue_plus 사용 + // ========================================================================= + + /// 주변 LunchPick 기기 스캔 + Future> scanNearbyDevices() async { + _discoveredDevices.clear(); + + // Bluetooth 상태 확인 + if (!await isBluetoothOn) { + throw Exception('블루투스가 꺼져 있습니다. 블루투스를 켜주세요.'); + } + + debugPrint('[BLE] 스캔 시작...'); + + // 스캔 결과 리스닝 + _scanSubscription = FlutterBluePlus.scanResults.listen( + (results) { + for (final result in results) { + _processScannedDevice(result); + } + }, + onError: (e) { + debugPrint('[BLE] 스캔 오류: $e'); + }, + ); + + try { + // 서비스 UUID로 필터링하여 스캔 + await FlutterBluePlus.startScan( + withServices: [Guid(BleConstants.serviceUuid)], + timeout: Duration(seconds: BleConstants.scanTimeoutSeconds), + ); + + // 스캔 완료 대기 + await Future.delayed( + Duration(seconds: BleConstants.scanTimeoutSeconds + 1), + ); + } catch (e) { + debugPrint('[BLE] 스캔 시작 오류: $e'); + } finally { + await _scanSubscription?.cancel(); + _scanSubscription = null; + } + + debugPrint('[BLE] 스캔 완료: ${_discoveredDevices.length}개 기기 발견'); + return List.from(_discoveredDevices); + } + + /// 스캔된 디바이스 처리 + void _processScannedDevice(ScanResult result) { + // 서비스 UUID 확인 + final hasService = result.advertisementData.serviceUuids.any( + (uuid) => + uuid.toString().toLowerCase() == + BleConstants.serviceUuid.toLowerCase(), + ); + + if (!hasService) return; + + // 광고 이름에서 공유 코드 추출 + final name = result.advertisementData.advName; + if (name.isEmpty) return; + + final code = name.startsWith(BleConstants.advertiseNamePrefix) + ? name.substring(BleConstants.advertiseNamePrefix.length) + : name; + + // 중복 방지 + if (_discoveredDevices.any((d) => d.deviceId == result.device.remoteId.str)) { + return; + } + + final device = ShareDevice( + code: code, + deviceId: result.device.remoteId.str, + remoteId: result.device.remoteId.str, + rssi: result.rssi, + discoveredAt: DateTime.now(), + bleDevice: result.device, + ); + + _discoveredDevices.add(device); + debugPrint('[BLE] 기기 발견: $code (RSSI: ${result.rssi})'); + } + + /// 맛집 리스트 전송 Future sendRestaurantList( String targetCode, List restaurants, ) async { - await Future.delayed(const Duration(seconds: 1)); - if (!_listeningDevices.containsKey(targetCode)) { - throw Exception('해당 코드를 찾을 수 없습니다.'); + // 대상 디바이스 찾기 + final targetDevice = _discoveredDevices.firstWhere( + (d) => d.code == targetCode, + orElse: () => throw Exception('대상 기기를 찾을 수 없습니다: $targetCode'), + ); + + final bleDevice = targetDevice.bleDevice; + if (bleDevice == null) { + throw Exception('BLE 디바이스 정보가 없습니다.'); } - final payload = jsonEncode( - restaurants - .map((restaurant) => _serializeRestaurant(restaurant)) - .toList(), + debugPrint('[BLE] 연결 시작: $targetCode'); + + // 연결 + await bleDevice.connect( + timeout: Duration(seconds: BleConstants.connectionTimeoutSeconds), ); - _incomingDataController.add(payload); + _connectedDevice = bleDevice; + + try { + // 서비스 탐색 + debugPrint('[BLE] 서비스 탐색 중...'); + final services = await bleDevice.discoverServices(); + + final targetService = services.firstWhere( + (s) => + s.uuid.toString().toLowerCase() == + BleConstants.serviceUuid.toLowerCase(), + orElse: () => throw Exception('LunchPick 서비스를 찾을 수 없습니다.'), + ); + + final dataCharacteristic = targetService.characteristics.firstWhere( + (c) => + c.uuid.toString().toLowerCase() == + BleConstants.dataCharacteristicUuid.toLowerCase(), + orElse: () => throw Exception('데이터 Characteristic을 찾을 수 없습니다.'), + ); + + // JSON 직렬화 + final payload = jsonEncode( + restaurants.map(_serializeRestaurant).toList(), + ); + + debugPrint('[BLE] 전송 데이터 크기: ${payload.length} bytes'); + + // 청킹 및 전송 + final chunks = _splitIntoChunks(payload, BleConstants.chunkSize); + debugPrint('[BLE] 청크 수: ${chunks.length}'); + + for (var i = 0; i < chunks.length; i++) { + final chunkMessage = 'CHUNK:$i:${chunks.length}:${chunks[i]}'; + final data = utf8.encode(chunkMessage); + + await dataCharacteristic.write( + data, + withoutResponse: false, + ); + + // 진행 상태 콜백 + onSendProgress?.call(i + 1, chunks.length); + + debugPrint('[BLE] 청크 ${i + 1}/${chunks.length} 전송 완료'); + + // 청크 간 딜레이 + if (i < chunks.length - 1) { + await Future.delayed( + Duration(milliseconds: BleConstants.chunkDelayMs), + ); + } + } + + debugPrint('[BLE] 전송 완료!'); + } finally { + // 연결 해제 + await bleDevice.disconnect(); + _connectedDevice = null; + debugPrint('[BLE] 연결 해제'); + } } - Map _serializeRestaurant(Restaurant restaurant) { - return { - 'id': restaurant.id, - 'name': restaurant.name, - 'category': restaurant.category, - 'subCategory': restaurant.subCategory, - 'description': restaurant.description, - 'phoneNumber': restaurant.phoneNumber, - 'roadAddress': restaurant.roadAddress, - 'jibunAddress': restaurant.jibunAddress, - 'latitude': restaurant.latitude, - 'longitude': restaurant.longitude, - 'lastVisitDate': restaurant.lastVisitDate?.toIso8601String(), - 'source': restaurant.source.name, - 'createdAt': restaurant.createdAt.toIso8601String(), - 'updatedAt': restaurant.updatedAt.toIso8601String(), - 'naverPlaceId': restaurant.naverPlaceId, - 'naverUrl': restaurant.naverUrl, - 'businessHours': restaurant.businessHours, - 'lastVisited': restaurant.lastVisited?.toIso8601String(), - 'visitCount': restaurant.visitCount, - }; + /// 문자열을 청크로 분할 + List _splitIntoChunks(String data, int chunkSize) { + final chunks = []; + for (var i = 0; i < data.length; i += chunkSize) { + final end = (i + chunkSize > data.length) ? data.length : i + chunkSize; + chunks.add(data.substring(i, end)); + } + return chunks; } + /// Restaurant를 JSON Map으로 변환 + Map _serializeRestaurant(Restaurant r) => { + 'id': r.id, + 'name': r.name, + 'category': r.category, + 'subCategory': r.subCategory, + 'description': r.description, + 'phoneNumber': r.phoneNumber, + 'roadAddress': r.roadAddress, + 'jibunAddress': r.jibunAddress, + 'latitude': r.latitude, + 'longitude': r.longitude, + 'lastVisitDate': r.lastVisitDate?.toIso8601String(), + 'source': r.source.name, + 'createdAt': r.createdAt.toIso8601String(), + 'updatedAt': r.updatedAt.toIso8601String(), + 'naverPlaceId': r.naverPlaceId, + 'naverUrl': r.naverUrl, + 'businessHours': r.businessHours, + 'lastVisited': r.lastVisited?.toIso8601String(), + 'visitCount': r.visitCount, + }; + + /// 스캔 중지 + Future stopScan() async { + await FlutterBluePlus.stopScan(); + await _scanSubscription?.cancel(); + _scanSubscription = null; + _discoveredDevices.clear(); + debugPrint('[BLE] 스캔 중지'); + } + + /// 리소스 정리 void dispose() { _incomingDataController.close(); - _listeningDevices.clear(); + _scanSubscription?.cancel(); + _connectedDevice?.disconnect(); + _cleanupPeripheral(); + debugPrint('[BLE] BluetoothService disposed'); } } diff --git a/lib/domain/entities/share_device.dart b/lib/domain/entities/share_device.dart index e0eb1f2..ad120cd 100644 --- a/lib/domain/entities/share_device.dart +++ b/lib/domain/entities/share_device.dart @@ -1,11 +1,68 @@ +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; + +/// BLE 공유를 위한 디바이스 정보 class ShareDevice { + /// 공유 코드 (6자리 숫자) final String code; + + /// 디바이스 고유 ID final String deviceId; + + /// BLE Remote ID (MAC 주소 또는 UUID) + final String? remoteId; + + /// 신호 강도 (RSSI) + final int? rssi; + + /// 발견 시각 final DateTime discoveredAt; + /// flutter_blue_plus BluetoothDevice 참조 + /// 실제 BLE 연결 시 사용 + final BluetoothDevice? bleDevice; + ShareDevice({ required this.code, required this.deviceId, + this.remoteId, + this.rssi, required this.discoveredAt, + this.bleDevice, }); + + /// 신호 강도 레벨 (0-4) + /// RSSI 값을 기반으로 계산 + int get signalLevel { + if (rssi == null) return 0; + if (rssi! >= -50) return 4; // 매우 강함 + if (rssi! >= -60) return 3; // 강함 + if (rssi! >= -70) return 2; // 보통 + if (rssi! >= -80) return 1; // 약함 + return 0; // 매우 약함 + } + + /// 디버그용 문자열 + @override + String toString() { + return 'ShareDevice(code: $code, deviceId: $deviceId, rssi: $rssi)'; + } + + /// 복사본 생성 + ShareDevice copyWith({ + String? code, + String? deviceId, + String? remoteId, + int? rssi, + DateTime? discoveredAt, + BluetoothDevice? bleDevice, + }) { + return ShareDevice( + code: code ?? this.code, + deviceId: deviceId ?? this.deviceId, + remoteId: remoteId ?? this.remoteId, + rssi: rssi ?? this.rssi, + discoveredAt: discoveredAt ?? this.discoveredAt, + bleDevice: bleDevice ?? this.bleDevice, + ); + } } diff --git a/lib/presentation/pages/share/share_screen.dart b/lib/presentation/pages/share/share_screen.dart index e349386..a4cb028 100644 --- a/lib/presentation/pages/share/share_screen.dart +++ b/lib/presentation/pages/share/share_screen.dart @@ -211,11 +211,11 @@ class _ShareScreenState extends ConsumerState { Text('이 코드를 상대방에게 알려주세요', style: AppTypography.caption(isDark)), const SizedBox(height: 16), TextButton.icon( - onPressed: () { + onPressed: () async { setState(() { _shareCode = null; }); - ref.read(bluetoothServiceProvider).stopListening(); + await ref.read(bluetoothServiceProvider).stopListening(); }, icon: const Icon(Icons.close), label: const Text('취소'), @@ -551,7 +551,7 @@ class _ShareScreenState extends ConsumerState { setState(() { _shareCode = null; }); - ref.read(bluetoothServiceProvider).stopListening(); + await ref.read(bluetoothServiceProvider).stopListening(); } void _showLoadingDialog(String message) { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 87e9e26..72e7679 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import ble_peripheral import flutter_blue_plus_darwin import flutter_local_notifications import geolocator_apple @@ -15,6 +16,7 @@ import url_launcher_macos import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + BlePeripheralPlugin.register(with: registry.registrar(forPlugin: "BlePeripheralPlugin")) FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 463fee8..9c12aff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + ble_peripheral: + dependency: "direct main" + description: + name: ble_peripheral + sha256: "858d370709507155bbf69e6160e9cb81d802e664f133a649a38b9ca04439884c" + url: "https://pub.dev" + source: hosted + version: "2.4.0" bluez: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ec60073..b04991b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,7 @@ dependencies: # Bluetooth flutter_blue_plus: ^1.31.0 + ble_peripheral: ^2.4.0 # Peripheral 역할 (Receiver) # Utilities uuid: ^4.2.1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8186d7f..9c2b085 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + BlePeripheralPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("BlePeripheralPluginCApi")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); PermissionHandlerWindowsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index fb6bd2e..439c682 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + ble_peripheral geolocator_windows permission_handler_windows share_plus