90 lines
3.0 KiB
Dart
90 lines
3.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:math';
|
|
|
|
import 'package:lunchpick/domain/entities/restaurant.dart';
|
|
import 'package:lunchpick/domain/entities/share_device.dart';
|
|
|
|
/// 실제 Bluetooth 통신을 대체하는 간단한 모의(Mock) 서비스.
|
|
class BluetoothService {
|
|
final _incomingDataController = StreamController<String>.broadcast();
|
|
final Map<String, ShareDevice> _listeningDevices = {};
|
|
final Random _random = Random();
|
|
|
|
Stream<String> get onDataReceived => _incomingDataController.stream;
|
|
|
|
/// 특정 코드로 수신 대기를 시작한다.
|
|
Future<void> startListening(String code) async {
|
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
|
stopListening();
|
|
final shareDevice = ShareDevice(
|
|
code: code,
|
|
deviceId: 'LP-${_random.nextInt(900000) + 100000}',
|
|
discoveredAt: DateTime.now(),
|
|
);
|
|
_listeningDevices[code] = shareDevice;
|
|
}
|
|
|
|
/// 더 이상 수신 대기하지 않는다.
|
|
void stopListening() {
|
|
if (_listeningDevices.isEmpty) return;
|
|
final codes = List<String>.from(_listeningDevices.keys);
|
|
for (final code in codes) {
|
|
_listeningDevices.remove(code);
|
|
}
|
|
}
|
|
|
|
/// 현재 주변에서 수신 대기 중인 기기 목록을 반환한다.
|
|
Future<List<ShareDevice>> scanNearbyDevices() async {
|
|
await Future<void>.delayed(const Duration(seconds: 1));
|
|
return _listeningDevices.values.toList();
|
|
}
|
|
|
|
/// 대상 코드로 맛집 리스트를 전송한다. 실제 BT 대신 JSON 문자열을 브로드캐스트한다.
|
|
Future<void> sendRestaurantList(
|
|
String targetCode,
|
|
List<Restaurant> restaurants,
|
|
) async {
|
|
await Future<void>.delayed(const Duration(seconds: 1));
|
|
if (!_listeningDevices.containsKey(targetCode)) {
|
|
throw Exception('해당 코드를 찾을 수 없습니다.');
|
|
}
|
|
|
|
final payload = jsonEncode(
|
|
restaurants
|
|
.map((restaurant) => _serializeRestaurant(restaurant))
|
|
.toList(),
|
|
);
|
|
_incomingDataController.add(payload);
|
|
}
|
|
|
|
Map<String, dynamic> _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,
|
|
};
|
|
}
|
|
|
|
void dispose() {
|
|
_incomingDataController.close();
|
|
_listeningDevices.clear();
|
|
}
|
|
}
|