Browse Source

Merge branch 'ble' of github.com:deepcloud2048/deep_voice into ble

weicu
wolfplus 1 year ago
parent
commit
b6a6f7219b
  1. 142
      lib/data/services/ble_manager.dart
  2. 2
      lib/modules/agent/controllers/agent_controller.dart
  3. 115
      lib/modules/agent/views/message_bubble.dart
  4. 156
      lib/modules/home/controllers/home_controller.dart
  5. 182
      lib/modules/home/views/home_view.dart
  6. 170
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  7. 0
      lib/modules/ota_upgrade/model/ota_upgrade_model.dart
  8. 200
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  9. 138
      lib/modules/pairing/controllers/pairing_controller.dart
  10. 1
      local_plugins/agent_service/android/build.gradle.kts
  11. 70
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  12. 60
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  13. 10
      local_plugins/ble_service/ios/Classes/BleService.swift
  14. 8
      local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift
  15. 11
      local_plugins/ble_service/lib/ble_service.dart
  16. 1
      local_plugins/music_service/android/build.gradle
  17. 9
      local_plugins/music_service/android/src/main/AndroidManifest.xml
  18. 87
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt
  19. 25
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt

142
lib/data/services/ble_manager.dart

@ -9,13 +9,15 @@ import 'package:deep_voice/data/models/appconfig_model.dart';
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;
import 'package:agent_service/agent_service.dart';
import 'package:classic_bluetooth/classic_bluetooth.dart';
/// 命令类型常量(与原生层保持一致)
class BleCommands {
static const int GET_VERSION = 0x01;
static const int GET_BATTERY_INFO = 0x02;
static const int CONTROL_CODEC = 0x03;
static const int PLAY_A2DP = 0x04;
static const int GET_PRODUCT_ID = 0x02;
static const int GET_PRODUCT_COLOR = 0x03;
static const int GET_BATTERY_INFO = 0x04;
static const int CONTROL_CODEC = 0x05;
}
/// 蓝牙设备信息缓存模型
@ -37,6 +39,8 @@ class DeviceInfo {
final bool caseCharging;
final String firmwareVersion;
final String firmwareProductId;
final String firmwareProductColor;
final DateTime lastUpdated;
DeviceInfo({
@ -50,6 +54,8 @@ class DeviceInfo {
this.rightEarCharging = false,
this.caseCharging = false,
this.firmwareVersion = '',
this.firmwareProductId = '',
this.firmwareProductColor = '',
DateTime? lastUpdated,
}) : lastUpdated = lastUpdated ?? DateTime.now();
@ -65,6 +71,8 @@ class DeviceInfo {
bool? rightEarCharging,
bool? caseCharging,
String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
}) {
return DeviceInfo(
address: address ?? this.address,
@ -77,6 +85,8 @@ class DeviceInfo {
rightEarCharging: rightEarCharging ?? this.rightEarCharging,
caseCharging: caseCharging ?? this.caseCharging,
firmwareVersion: firmwareVersion ?? this.firmwareVersion,
firmwareProductId: firmwareProductId ?? this.firmwareProductId,
firmwareProductColor: firmwareProductColor ?? this.firmwareProductColor,
lastUpdated: DateTime.now(),
);
}
@ -96,6 +106,7 @@ class BleManager extends GetxService {
// BLE服务实例
final BleService _bleService = BleService();
// 经典蓝牙实例
// BLE连接状态
final _connectionState = BleConnectionState.disconnected.obs;
@ -106,8 +117,7 @@ class BleManager extends GetxService {
bool get isConnected => _isConnected.value;
// 设备信息缓存
final Rx<DeviceInfo?> _deviceInfo = Rx<DeviceInfo?>(null);
DeviceInfo? get deviceInfo => _deviceInfo.value;
final deviceInfo = Rx<DeviceInfo?>(null); // 使用 Rx 包装
// 订阅
StreamSubscription<dynamic>? _statusSubscription;
@ -145,8 +155,8 @@ class BleManager extends GetxService {
if (event is Map) {
// 处理连接状态更新
if (event.containsKey('connectionState')) {
final state = event['connectionState'] as int;
if (event['type'] == 'connectionStateChanged') {
final state = event['state'] as int;
_connectionState.value = state;
// 连接状态转换
@ -154,8 +164,13 @@ class BleManager extends GetxService {
Logger.info('BLE设备已连接');
_isConnected.value = true;
// 在连接成功后获取设备信息
_fetchDeviceInfo();
// 在连接成功后延迟 300 毫秒获取设备信息(可根据需要调整时间)
Future.delayed(const Duration(milliseconds: 300), () {
// 可选:检查是否仍处于连接状态
if (_isConnected.value) {
fetchDeviceInfo();
}
});
} else if (state == BleConnectionState.disconnected ||
state == BleConnectionState.error) {
Logger.info('BLE设备已断开或发生错误');
@ -218,12 +233,18 @@ class BleManager extends GetxService {
void _processDeviceInfoByCommandId(
int commandId, Map<dynamic, dynamic> data) {
switch (commandId) {
case BleCommands.GET_BATTERY_INFO:
_updateBatteryInfoFromData(data);
break;
case BleCommands.GET_VERSION:
_updateVersionInfoFromData(data);
break;
case BleCommands.GET_PRODUCT_ID:
_updateProductIdFromData(data);
break;
case BleCommands.GET_PRODUCT_COLOR:
break;
case BleCommands.GET_BATTERY_INFO:
_updateBatteryInfoFromData(data);
break;
default:
Logger.info('未处理的命令ID: $commandId');
}
@ -239,6 +260,11 @@ class BleManager extends GetxService {
case 'versionInfo':
_updateVersionInfoFromData(data);
break;
case 'productId':
_updateProductIdFromData(data);
break;
case 'productColor':
break;
default:
Logger.info('未处理的命令名称: $commandName');
}
@ -307,6 +333,29 @@ class BleManager extends GetxService {
}
}
/// 更新设备id
void _updateProductIdFromData(Map<dynamic, dynamic> data) {
try {
// 确保数据中包含版本信息
if (!data.containsKey('productId')) {
Logger.warning('版本信息数据不完整');
return;
}
// 提取版本信息
final String productId = data['productId'] as String? ?? '';
// 更新设备信息
_updateDeviceInfoIfNeeded(
firmwareProductId: productId,
);
Logger.info('设备设备id已更新: 设备id=$productId');
} catch (e) {
Logger.error('更新设备id失败: $e');
}
}
/// 更新设备信息(如果已存在设备)
void _updateDeviceInfoIfNeeded({
int? batteryLevel,
@ -317,31 +366,53 @@ class BleManager extends GetxService {
bool? rightEarCharging,
bool? caseCharging,
String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
}) {
// 如果设备信息已存在,则更新
if (_deviceInfo.value != null) {
_deviceInfo.value = _deviceInfo.value!.copyWith(
batteryLevel: batteryLevel,
leftEarBattery: leftEarBattery,
rightEarBattery: rightEarBattery,
caseBattery: caseBattery,
leftEarCharging: leftEarCharging,
rightEarCharging: rightEarCharging,
caseCharging: caseCharging,
firmwareVersion: firmwareVersion,
if (deviceInfo.value != null) {
deviceInfo.value = deviceInfo.value!.copyWith(
batteryLevel: batteryLevel ?? deviceInfo.value?.batteryLevel,
leftEarBattery: leftEarBattery ?? deviceInfo.value?.leftEarBattery,
rightEarBattery: rightEarBattery ?? deviceInfo.value?.rightEarBattery,
caseBattery: caseBattery ?? deviceInfo.value?.caseBattery,
leftEarCharging: leftEarCharging ?? deviceInfo.value!.leftEarCharging,
rightEarCharging:
rightEarCharging ?? deviceInfo.value!.rightEarCharging,
caseCharging: caseCharging ?? deviceInfo.value!.caseCharging,
firmwareVersion: firmwareVersion ?? deviceInfo.value!.firmwareVersion,
firmwareProductId:
firmwareProductId ?? deviceInfo.value!.firmwareProductId,
firmwareProductColor:
firmwareProductColor ?? deviceInfo.value!.firmwareProductColor,
);
}
// 创建设备信息
// 如果设备信息不存在,但有地址和名称,则创建新的设备信息
else if (_deviceInfo.value == null &&
else if (deviceInfo.value == null &&
_isConnected.value &&
(firmwareVersion != null || batteryLevel != null)) {
// 提示尝试创建设备信息但缺少必要信息
Logger.warning('尝试更新设备信息,但设备基本信息不存在');
deviceInfo.value = DeviceInfo(
address: "", // 应确保从连接信息中获取真实值
name: "",
batteryLevel: batteryLevel ?? 0,
leftEarBattery: leftEarBattery ?? 0,
rightEarBattery: rightEarBattery ?? 0,
caseBattery: caseBattery ?? 0,
leftEarCharging: leftEarCharging ?? false,
rightEarCharging: rightEarCharging ?? false,
caseCharging: caseCharging ?? false,
firmwareVersion: firmwareVersion ?? '',
firmwareProductId: firmwareProductId ?? '',
firmwareProductColor: firmwareProductColor ?? '',
);
}
}
/// 连接成功后获取设备信息
Future<void> _fetchDeviceInfo() async {
Future<void> fetchDeviceInfo() async {
Logger.info('开始获取BLE设备详细信息');
try {
@ -360,6 +431,14 @@ class BleManager extends GetxService {
} else {
Logger.warning('版本信息获取指令发送失败');
}
// 获取产品id
final productIdResult = await _bleService.getProductId();
if (productIdResult) {
Logger.info('产品id获取指令已发送');
} else {
Logger.warning('产品id获取指令发送失败');
}
} catch (e) {
Logger.error('获取BLE设备信息失败: $e');
}
@ -450,9 +529,9 @@ class BleManager extends GetxService {
_connectionState.value = BleConnectionState.connected;
// 如果设备信息存在,则获取详细信息
if (_deviceInfo.value != null) {
if (deviceInfo.value != null) {
// 获取详细设备信息
_fetchDeviceInfo();
fetchDeviceInfo();
}
}
} else {
@ -486,7 +565,6 @@ class BleManager extends GetxService {
}
}
/// 获取电池电量
Future<int> getBatteryLevel() async {
try {
@ -507,6 +585,16 @@ class BleManager extends GetxService {
}
}
/// 主动产品id
Future<bool> getProductId() async {
try {
return await _bleService.getProductId();
} catch (e) {
Logger.error('获取设备版本失败: $e');
return false;
}
}
/// 主动获取设备版本信息
Future<bool> fetchDeviceVersion() async {
try {

2
lib/modules/agent/controllers/agent_controller.dart

@ -622,7 +622,7 @@ class AgentController extends GetxController {
// 播放音乐
void musicStatus() async {
var status = await _musiceManager.getStatus();
logger.i('获取当前音乐播放状态 $status');
// logger.i('获取当前音乐播放状态 $status');
isPlaying.value = status['isPlaying'];
currentUrl.value = status['url'];
}

115
lib/modules/agent/views/message_bubble.dart

@ -764,7 +764,7 @@ class MessageBubble extends GetView<AgentController> {
),
],
),
Icon(
const Icon(
Icons.wb_sunny,
size: 48,
color: Colors.white,
@ -777,7 +777,7 @@ class MessageBubble extends GetView<AgentController> {
color: Colors.white.withOpacity(0.2),
),
SizedBox(height: 16),
Text(
const Text(
'未来预报',
style: TextStyle(
fontSize: 14,
@ -803,11 +803,15 @@ class MessageBubble extends GetView<AgentController> {
(index) {
String dateStr = days[index]['date'];
DateTime? date = DateUtil.getDateTime(dateStr);
String weekday = DateUtil.getWeekday(date);
IconData weatherIcon =
getWeatherIcon(days[index]['weatherCondition']);
String weekday = DateUtil.getWeekday(date,
languageCode: "zh", short: true);
String temp = "${days[index]['temperature']}°";
return _buildForecastDay(
weekday,
Icons.wb_sunny,
days[index]['temperature'],
weatherIcon,
temp,
);
},
),
@ -915,6 +919,107 @@ class MessageBubble extends GetView<AgentController> {
],
);
}
/// 获取天气图标
IconData getWeatherIcon(String description) {
description = description.trim();
if (["晴", "少云", "晴间多云"].contains(description)) {
return Icons.wb_sunny;
}
if (["多云", "阴"].contains(description)) {
return Icons.cloud;
}
if (description.contains("雾") ||
["雾", "浓雾", "强浓雾", "轻雾", "大雾", "特强浓雾", "霾", "中度霾", "重度霾", "严重霾"]
.contains(description)) {
return Icons.blur_on;
}
if (description.contains("风") ||
[
"有风",
"平静",
"微风",
"和风",
"清风",
"强风/劲风",
"疾风",
"大风",
"烈风",
"风暴",
"狂爆风",
"飓风",
"热带风暴"
].contains(description)) {
return Icons.air;
}
if (description.contains("雨") ||
[
"阵雨",
"雷阵雨",
"雷阵雨并伴有冰雹",
"小雨",
"中雨",
"大雨",
"暴雨",
"大暴雨",
"特大暴雨",
"强阵雨",
"强雷阵雨",
"极端降雨",
"毛毛雨/细雨",
"雨",
"小雨-中雨",
"中雨-大雨",
"大雨-暴雨",
"暴雨-大暴雨",
"大暴雨-特大暴雨"
].contains(description)) {
return Icons.grain; // 或使用 community icon 包:weather_icons 中的 rain
}
if (description.contains("雪") ||
[
"雨雪天气",
"雨夹雪",
"阵雨夹雪",
"冻雨",
"雪",
"阵雪",
"小雪",
"中雪",
"大雪",
"暴雪",
"小雪-中雪",
"中雪-大雪",
"大雪-暴雪"
].contains(description)) {
return Icons.ac_unit;
}
if (["浮尘", "扬沙", "沙尘暴", "强沙尘暴"].contains(description)) {
return Icons.filter_drama;
}
if (["龙卷风"].contains(description)) {
return Icons.waves;
}
if (["热"].contains(description)) {
return Icons.local_fire_department;
}
if (["冷"].contains(description)) {
return Icons.ac_unit;
}
// 默认或未知天气
return Icons.help_outline;
}
}
/// 动画点组件

156
lib/modules/home/controllers/home_controller.dart

@ -19,7 +19,7 @@ enum ConnectionStatus {
/// 主页控制器,管理设备连接状态和信息展示
class HomeController extends GetxController {
static HomeController get to => Get.find();
// 依赖的控制器
final ExploreController exploreController = Get.find<ExploreController>();
@ -27,31 +27,47 @@ class HomeController extends GetxController {
final _connectionStatus = ConnectionStatus.disconnected.obs;
final _connectionErrorMsg = ''.obs;
final _bluetoothState = 'unknown'.obs;
// Getter方法
ConnectionStatus get connectionStatus => _connectionStatus.value;
String get connectionErrorMsg => _connectionErrorMsg.value;
String get bluetoothState => _bluetoothState.value;
BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice;
DeviceInfo? get deviceInfo => _bleManager.deviceInfo;
Rx<DeviceInfo?> get deviceInfo => _bleManager.deviceInfo;
bool get isConnected => currentDevice != null;
// 设备详细信息的getter,从deviceInfo获取数据
int get leftEarBattery => deviceInfo?.leftEarBattery ?? 0;
int get rightEarBattery => deviceInfo?.rightEarBattery ?? 0;
int get caseBattery => deviceInfo?.caseBattery ?? 0;
bool get leftEarCharging => deviceInfo?.leftEarCharging ?? false;
bool get rightEarCharging => deviceInfo?.rightEarCharging ?? false;
bool get caseCharging => deviceInfo?.caseCharging ?? false;
String get firmwareVersion => deviceInfo?.firmwareVersion ?? '';
final leftEarBattery = 0.obs;
final rightEarBattery = 0.obs;
final caseBattery = 0.obs;
final leftEarCharging = false.obs;
final rightEarCharging = false.obs;
final caseCharging = false.obs;
final firmwareVersion = ''.obs;
// int get leftEarBattery => deviceInfo?.leftEarBattery ?? 0;
// int get rightEarBattery => deviceInfo?.rightEarBattery ?? 0;
// int get caseBattery => deviceInfo?.caseBattery ?? 0;
// bool get leftEarCharging => deviceInfo?.leftEarCharging ?? false;
// bool get rightEarCharging => deviceInfo?.rightEarCharging ?? false;
// bool get caseCharging => deviceInfo?.caseCharging ?? false;
// String get firmwareVersion => deviceInfo?.firmwareVersion ?? '';
/// 当有新的deviceInfo时更新所有Observable变量
void updateDeviceInfo(DeviceInfo? newDeviceInfo) {
leftEarBattery.value = newDeviceInfo?.leftEarBattery ?? 0;
rightEarBattery.value = newDeviceInfo?.rightEarBattery ?? 0;
caseBattery.value = newDeviceInfo?.caseBattery ?? 0;
leftEarCharging.value = newDeviceInfo?.leftEarCharging ?? false;
rightEarCharging.value = newDeviceInfo?.rightEarCharging ?? false;
caseCharging.value = newDeviceInfo?.caseCharging ?? false;
firmwareVersion.value = newDeviceInfo?.firmwareVersion ?? '';
}
// 监听和订阅
Worker? _deviceInfoWorker;
Worker? _bluetoothStateWorker;
Worker? _deviceWorker;
// 蓝牙服务
final _bluetoothManager = Get.find<BluetoothManager>();
final _bleManager = Get.find<BleManager>();
@ -60,7 +76,7 @@ class HomeController extends GetxController {
super.onInit();
_initWithCachedInfo();
}
@override
void onClose() {
_deviceInfoWorker?.dispose();
@ -73,11 +89,12 @@ class HomeController extends GetxController {
void _initWithCachedInfo() {
// 使用蓝牙服务中的当前状态
_bluetoothState.value = _bluetoothManager.bluetoothState;
// 根据当前设备状态设置连接状态
if (currentDevice != null) {
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
updateDeviceInfo(_bleManager.deviceInfo.value);
Logger.info('主页加载缓存设备信息: ${currentDevice!.name}');
} else {
_connectionStatus.value = ConnectionStatus.disconnected;
@ -87,67 +104,73 @@ class HomeController extends GetxController {
_connectionErrorMsg.value = '未连接到设备';
}
}
// 设置监听器监听蓝牙服务状态变化
_setupStateListeners();
}
/// 设置状态监听器
void _setupStateListeners() {
// 设备信息监听 - 设备信息更新时自动触发UI更新
_deviceInfoWorker = ever(_bleManager.deviceInfo.obs, (DeviceInfo? info) {
_deviceInfoWorker = ever(_bleManager.deviceInfo, (DeviceInfo? info) {
Logger.info('设备信息监听');
if (info != null) {
Logger.info('主页更新设备信息: ${info.toString()}');
updateDeviceInfo(info);
} else {
Logger.info('主页清空设备信息');
}
update();
});
// 蓝牙状态监听
_bluetoothStateWorker = ever(_bluetoothManager.bluetoothState.obs, (String state) {
_bluetoothStateWorker =
ever(_bluetoothManager.bluetoothState.obs, (String state) {
_bluetoothState.value = state;
// 如果蓝牙关闭,更新错误信息
if (state == 'off') {
_connectionErrorMsg.value = '蓝牙未开启';
_connectionStatus.value = ConnectionStatus.disconnected;
}
update();
});
// 设备连接状态监听 - 直接监听设备变化
_deviceWorker = ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
_deviceWorker =
ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
if (device != null) {
// 设备已连接
if (_connectionStatus.value != ConnectionStatus.connected) {
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
Logger.info('主页检测到设备已连接: ${device.name}');
}
} else if (_connectionStatus.value == ConnectionStatus.connected) {
// 设备已断开
_connectionStatus.value = ConnectionStatus.disconnected;
_connectionErrorMsg.value = '设备已断开连接';
Logger.info('主页检测到设备已断开连接');
}
update();
});
}
/// 刷新设备连接状态
/// 注意:此方法不会主动刷新设备,只更新UI状态
Future<void> refreshDeviceConnection() async {
// 更新当前状态以反映UI变化
bool hasDevice = currentDevice != null;
if (hasDevice) {
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
await _bleManager.fetchDeviceInfo();
Logger.info('使用缓存设备信息: ${currentDevice!.name}');
} else {
if (_bluetoothState.value == 'off') {
@ -157,13 +180,13 @@ class HomeController extends GetxController {
}
_connectionStatus.value = ConnectionStatus.disconnected;
}
// 刷新UI
update();
}
// 导航和功能方法
/// 重新扫描设备
void rescanDevices() {
Get.toNamed(Routes.pairing);
@ -185,7 +208,7 @@ class HomeController extends GetxController {
}
// UI辅助方法
/// 获取连接状态对应的颜色
Color getConnectionStatusColor() {
switch (_connectionStatus.value) {
@ -244,62 +267,71 @@ class HomeController extends GetxController {
/// 获取设备ID
String getDeviceId() {
return deviceInfo?.address ?? currentDevice?.address ?? 'unknown'.tr;
return deviceInfo.value?.address ?? currentDevice?.address ?? 'unknown'.tr;
}
/// 获取设备电量文本
String getBatteryText() {
return deviceInfo?.batteryLevel != null && deviceInfo!.batteryLevel > 0
? '${deviceInfo!.batteryLevel}%'
: 'unknown'.tr;
return deviceInfo.value?.batteryLevel != null &&
deviceInfo.value!.batteryLevel > 0
? '${deviceInfo.value?.batteryLevel}%'
: 'unknown'.tr;
}
/// 获取耳机左右电量文本
String getEarbudsText() {
if (deviceInfo == null || (deviceInfo!.leftEarBattery <= 0 && deviceInfo!.rightEarBattery <= 0)) {
if (deviceInfo == null ||
(deviceInfo.value!.leftEarBattery <= 0 &&
deviceInfo.value!.rightEarBattery <= 0)) {
return 'unknown'.tr;
}
String leftStatus = deviceInfo!.leftEarBattery > 0 ? '${deviceInfo!.leftEarBattery}%' : 'N/A';
String rightStatus = deviceInfo!.rightEarBattery > 0 ? '${deviceInfo!.rightEarBattery}%' : 'N/A';
leftStatus = deviceInfo!.leftEarCharging ? '$leftStatus ⚡' : leftStatus;
rightStatus = deviceInfo!.rightEarCharging ? '$rightStatus ⚡' : rightStatus;
String leftStatus = deviceInfo.value!.leftEarBattery > 0
? '${deviceInfo.value!.leftEarBattery}%'
: 'N/A';
String rightStatus = deviceInfo.value!.rightEarBattery > 0
? '${deviceInfo.value!.rightEarBattery}%'
: 'N/A';
leftStatus =
deviceInfo.value!.leftEarCharging ? '$leftStatus ⚡' : leftStatus;
rightStatus =
deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus;
return '左耳: $leftStatus / 右耳: $rightStatus';
}
/// 获取充电仓电量文本
String getCaseText() {
if (deviceInfo == null || deviceInfo!.caseBattery <= 0) {
if (deviceInfo.value == null || deviceInfo.value!.caseBattery <= 0) {
return 'unknown'.tr;
}
String caseStatus = '${deviceInfo!.caseBattery}%';
if (deviceInfo!.caseCharging) {
String caseStatus = '${deviceInfo.value!.caseBattery}%';
if (deviceInfo.value!.caseCharging) {
caseStatus = '$caseStatus ⚡';
}
return '充电仓: $caseStatus';
}
/// 获取充电状态文本
String getChargingText() {
if (deviceInfo == null) {
return 'unknown'.tr;
}
bool isCharging = deviceInfo!.leftEarCharging ||
deviceInfo!.rightEarCharging ||
deviceInfo!.caseCharging;
bool isCharging = deviceInfo.value!.leftEarCharging ||
deviceInfo.value!.rightEarCharging ||
deviceInfo.value!.caseCharging;
return isCharging ? 'charging'.tr : 'notCharging'.tr;
}
/// 获取设备版本文本
String getVersionText() {
return deviceInfo?.firmwareVersion.isNotEmpty == true ?
deviceInfo!.firmwareVersion : 'unknown'.tr;
return deviceInfo.value?.firmwareVersion.isNotEmpty == true
? deviceInfo.value!.firmwareVersion
: 'unknown'.tr;
}
}

182
lib/modules/home/views/home_view.dart

@ -160,43 +160,43 @@ class HomeView extends GetView<HomeController> {
child: ClipRRect(
// 添加 ClipRRect 来实现圆角
borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小
child: SizedBox(
width: double.infinity,
height: 200.h,
child: Image.asset(
isDarkMode
? 'assets/images/robot_light.png'
: 'assets/images/robot_light.png',
fit: BoxFit.fitWidth,
child: SizedBox(
width: double.infinity,
height: 200.h,
child: Image.asset(
isDarkMode
? 'assets/images/robot_light.png'
: 'assets/images/robot_light.png',
fit: BoxFit.fitWidth,
),
),
),
),
),
Center(
child: ClipRRect(
// 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r),
child: Image.asset(
isDarkMode
? 'assets/images/headphone_dark.png'
: 'assets/images/headphone_light.png',
height: 180.h,
fit: BoxFit.contain,
child: Image.asset(
isDarkMode
? 'assets/images/headphone_dark.png'
: 'assets/images/headphone_light.png',
height: 180.h,
fit: BoxFit.contain,
),
),
),
),
Center(
child: ClipRRect(
// 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r),
child: SizedBox(
width: double.infinity,
height: 200.h,
child: Image.asset(
isDarkMode
? 'assets/images/glasses_light.png'
: 'assets/images/glasses_light.png',
fit: BoxFit.fitWidth,
child: SizedBox(
width: double.infinity,
height: 200.h,
child: Image.asset(
isDarkMode
? 'assets/images/glasses_light.png'
: 'assets/images/glasses_light.png',
fit: BoxFit.fitWidth,
),
),
),
@ -283,50 +283,52 @@ class HomeView extends GetView<HomeController> {
],
),
Obx(() => Row(
children: [
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: controller.getConnectionStatusColor(),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: controller.getConnectionStatusColor().withOpacity(0.3),
blurRadius: 4,
spreadRadius: 1,
children: [
Container(
width: 8.w,
height: 8.w,
decoration: BoxDecoration(
color: controller.getConnectionStatusColor(),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: controller
.getConnectionStatusColor()
.withOpacity(0.3),
blurRadius: 4,
spreadRadius: 1,
),
],
),
],
),
),
SizedBox(width: 4.w),
Text(
controller.getConnectionStatusText(),
style: TextStyle(
fontSize: 12.sp,
color: controller.getConnectionStatusColor(),
),
),
// 添加刷新按钮
SizedBox(width: 8.w),
InkWell(
onTap: () => controller.refreshDeviceConnection(),
borderRadius: BorderRadius.circular(12.r),
child: Container(
padding: EdgeInsets.all(4.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
shape: BoxShape.circle,
),
child: Icon(
Icons.refresh,
size: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
SizedBox(width: 4.w),
Text(
controller.getConnectionStatusText(),
style: TextStyle(
fontSize: 12.sp,
color: controller.getConnectionStatusColor(),
),
),
),
),
],
)),
// 添加刷新按钮
SizedBox(width: 8.w),
InkWell(
onTap: () => controller.refreshDeviceConnection(),
borderRadius: BorderRadius.circular(12.r),
child: Container(
padding: EdgeInsets.all(4.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
shape: BoxShape.circle,
),
child: Icon(
Icons.refresh,
size: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
),
),
],
)),
],
);
}
@ -338,9 +340,8 @@ class HomeView extends GetView<HomeController> {
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.red[900]!.withOpacity(0.2)
: Colors.red[50],
color:
isDarkMode ? Colors.red[900]!.withOpacity(0.2) : Colors.red[50],
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isDarkMode ? Colors.red[800]! : Colors.red[200]!),
@ -440,12 +441,12 @@ class HomeView extends GetView<HomeController> {
final leftBattery = controller.leftEarBattery;
final rightBattery = controller.rightEarBattery;
final caseBattery = controller.caseBattery;
// 获取充电状态
final leftEarCharging = controller.leftEarCharging;
final rightEarCharging = controller.rightEarCharging;
final caseCharging = controller.caseCharging;
return GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
@ -457,33 +458,33 @@ class HomeView extends GetView<HomeController> {
_buildBatteryGridItem(
icon: Icons.headphones,
label: '左耳机',
batteryLevel: leftBattery,
isCharging: leftEarCharging,
batteryLevel: leftBattery.value,
isCharging: leftEarCharging.value,
isDarkMode: isDarkMode,
),
// 右耳机
_buildBatteryGridItem(
icon: Icons.headphones,
label: '右耳机',
batteryLevel: rightBattery,
isCharging: rightEarCharging,
batteryLevel: rightBattery.value,
isCharging: rightEarCharging.value,
isDarkMode: isDarkMode,
),
// 充电盒
_buildBatteryGridItem(
icon: Icons.charging_station,
label: '充电盒',
batteryLevel: caseBattery,
isCharging: caseCharging,
batteryLevel: caseBattery.value,
isCharging: caseCharging.value,
isDarkMode: isDarkMode,
),
],
);
});
}
// 单个电池网格项
Widget _buildBatteryGridItem({
required IconData icon,
@ -495,7 +496,7 @@ class HomeView extends GetView<HomeController> {
// 获取电池图标和颜色
IconData batteryIcon;
Color batteryColor;
if (batteryLevel > 75) {
batteryIcon = Icons.battery_full;
batteryColor = Colors.green[600]!;
@ -512,16 +513,16 @@ class HomeView extends GetView<HomeController> {
batteryIcon = Icons.battery_alert;
batteryColor = Colors.red[600]!;
}
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode ?
Colors.black.withOpacity(0.1) :
Colors.grey.withOpacity(0.1),
color: isDarkMode
? Colors.black.withOpacity(0.1)
: Colors.grey.withOpacity(0.1),
blurRadius: 4,
spreadRadius: 0,
offset: Offset(0, 2),
@ -545,7 +546,7 @@ class HomeView extends GetView<HomeController> {
),
),
SizedBox(height: 6.h),
// 标签
Text(
label,
@ -557,7 +558,7 @@ class HomeView extends GetView<HomeController> {
textAlign: TextAlign.center,
),
SizedBox(height: 6.h),
// 电池指示器
Row(
mainAxisAlignment: MainAxisAlignment.center,
@ -614,16 +615,15 @@ class HomeView extends GetView<HomeController> {
isDarkMode: isDarkMode,
),
SizedBox(height: 8.h),
// 固件版本
_buildInfoRow(
icon: Icons.info_outline,
label: 'firmwareVersion'.tr,
value: controller.firmwareVersion,
value: controller.firmwareVersion.value,
isDarkMode: isDarkMode,
),
// 连接状态标签
SizedBox(height: 10.h),
Container(
@ -763,7 +763,7 @@ class HomeView extends GetView<HomeController> {
),
);
}
// 热门AI体验标题
Widget _buildSectionTitle(bool isDarkMode) {
return Row(

170
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -1,19 +1,17 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:path/path.dart';
import 'dart:async';
import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import '../../../data/services/network/api.dart';
import '../ota.dart';
import '../model/ota_upgrade_model.dart';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:json_annotation/json_annotation.dart';
import '../../../data/services/ble_manager.dart';
class OtaUpgradeController extends GetxController {
final RxString errorMessage = ''.obs;
final RxBool isFetchingDeviceInfo = false.obs;
final Dio _dio = Dio();
final RxString latestVersion = ''.obs;
final RxString currentVersion = ''.obs;
@ -24,41 +22,93 @@ class OtaUpgradeController extends GetxController {
final RxDouble progress = 0.0.obs;
// 蓝牙服务
final _bleManager = Get.find<BleManager>();
@override
void onInit() {
super.onInit();
_getCurrentVersion();
}
// 获取当前版本信息
// 添加重试方法
void retryConnection() {
errorMessage.value = '';
_getCurrentVersion();
}
// 添加更新方法
Future<void> startUpdate() async {
try {
isUpdating.value = true;
// 实现更新逻辑...
} catch (e) {
errorMessage.value = e.toString();
Get.snackbar('更新失败', e.toString());
} finally {
isUpdating.value = false;
}
}
Future<void> _getCurrentVersion() async {
//发送ble获取固件版本
var firmwareVersion = "1.1.0";
currentVersion.value = firmwareVersion;
//发送ble获取产品id
var id = "a2";
//发送api请求
final response = await Api.getequipmentinfo({
"id": id,
});
if (response['info'] != '') {
print('getLoginToken:进来了');
// 根据API设计调整
final resp = OtaConfigResp.fromJson(response);
try {
// 添加获取设备信息的重试机制
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
// 第一次检查设备信息
if (deviceInfo == null) {
// 显示加载提示
isFetchingDeviceInfo.value = true;
// 尝试重新获取设备信息
await _bleManager.fetchDeviceInfo();
// 等待后再次获取设备信息
deviceInfo = _bleManager.deviceInfo.value;
isFetchingDeviceInfo.value = false;
// 仍然为空则抛出异常
if (deviceInfo == null) {
throw const FormatException('无法获取设备信息,请检查设备连接');
}
}
// 检查固件信息有效性
if (deviceInfo.firmwareVersion.isEmpty
//||
// deviceInfo.firmwareProductId.isEmpty
) {
throw const FormatException('设备固件信息不完整');
}
// 正常流程
currentVersion.value = deviceInfo.firmwareVersion;
print('成功解析: ${resp.id}, ${resp.factory}');
// API请求
final response = await Api.getequipmentinfo({
"id": "a2",
});
// 处理API响应
if (response['info'] == null) {
throw const FormatException('API 响应格式错误');
}
final resp = OtaConfigResp.fromJson(response);
latestVersion.value = resp.systemversion!;
updatepackageaddress = resp.updatepackageaddress!;
if (_isNewVersionAvailable(resp.systemversion!)) {
//需要升级
print('需要更新');
isUpdateAvailable.value = false;
} else {
isUpdateAvailable.value = true;
//已经是最新版本更新ui,按钮隐藏
}
} else {
throw const FormatException('API 响应缺少 "info" 字段');
// 版本检查
isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
} catch (e) {
// 统一错误处理
errorMessage.value = e.toString();
Get.snackbar('错误', e.toString(),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white);
// 重要:重新抛出异常以便上层处理(可选)
} finally {
isFetchingDeviceInfo.value = false;
}
}
@ -120,13 +170,10 @@ class OtaUpgradeController extends GetxController {
);
}
// 开始下载
// 修改后的startDownload方法
Future<void> startDownload() async {
isUpdating.value = true;
// 查找最后一个 '/' 的位置
int lastIndex = updatepackageaddress.lastIndexOf('/');
// 截取最后一个 '/' 后面的字符串
String fileName = updatepackageaddress.substring(lastIndex + 1);
String directoryName = "ota_upgrades";
@ -145,14 +192,54 @@ class OtaUpgradeController extends GetxController {
throw FileSystemException('无法访问下载目录');
}
// 创建子目录:Download/APP
final targetDir = Directory('${downloadsDir.path}/$directoryName');
if (!await targetDir.exists()) {
await targetDir.create(recursive: true);
}
final savePath = '${targetDir.path}/$fileName';
print('文件保存路径: $savePath'); // 关键日志
// 新增:检查同名文件逻辑
bool existingSameFile = false;
try {
var files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
String name = basename(entity.path); // 使用path包中的basename
if (name == fileName) {
existingSameFile = true;
break;
}
}
}
} catch (e) {
Get.snackbar('错误', '检查本地文件失败: $e');
isUpdating.value = false;
return;
}
if (existingSameFile) {
Get.snackbar('文件已存在', '跳过下载,直接更新');
isUpdating.value = false;
_installApp(savePath);
return;
}
// 新增:删除其他文件逻辑
try {
var files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
await entity.delete();
}
}
} catch (e) {
Get.snackbar('错误', '删除旧文件失败: $e');
isUpdating.value = false;
return;
}
print('文件保存路径: $savePath');
final options = Options(
receiveTimeout: Duration.zero,
@ -181,22 +268,23 @@ class OtaUpgradeController extends GetxController {
},
);
// 下载完成后验证文件存在性
File file = File(savePath);
if (await file.exists()) {
Get.snackbar('下载完成', '文件已保存至: $savePath'); // 明确提示路径
Get.snackbar('下载完成', '文件已保存至: $savePath');
} else {
Get.snackbar('错误', '文件未找到,请检查写入权限');
}
isUpdating.value = false;
//下载完成,开始升级
_installApp(savePath);
} on DioException catch (e) {
Get.snackbar('下载失败', '错误: ${e.message}');
isUpdating.value = false;
} on FileSystemException catch (e) {
Get.snackbar('存储失败', '无法写入文件: ${e.message}');
isUpdating.value = false;
} catch (e) {
Get.snackbar('未知错误', e.toString());
isUpdating.value = false;
}
}

0
lib/modules/ota_upgrade/ota.dart → lib/modules/ota_upgrade/model/ota_upgrade_model.dart

200
lib/modules/ota_upgrade/views/ota_upgrade_view.dart

@ -4,106 +4,126 @@ import '../controllers/ota_upgrade_controller.dart';
class OtaUpgradeView extends GetView<OtaUpgradeController> {
const OtaUpgradeView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
// 添加外层Center
child: SingleChildScrollView(
// 添加滚动防止小屏溢出
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, // 水平居中
mainAxisSize: MainAxisSize.min, // 紧凑布局
children: [
// 设备图标
CircleAvatar(
radius: 60,
backgroundColor: Colors.blue[100],
child: Icon(
Icons.phone_iphone,
size: 60,
color: Colors.blue,
),
),
SizedBox(height: 30),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 600), // 限制最大宽度
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 设备图标
_buildDeviceIcon(),
SizedBox(height: 30),
// 版本信息
Column(
children: [
Text(
'当前版本',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
SizedBox(height: 8),
Text(
'v${controller.currentVersion}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
SizedBox(height: 40),
// 版本信息
_buildVersionInfo(),
SizedBox(height: 40),
// 更新按钮/进度条
Obx(() {
if (controller.isUpdating.value) {
return Column(
children: [
SizedBox(
width: 200, // 限制进度条宽度
child: LinearProgressIndicator(
value: controller.downloadProgress.value,
backgroundColor: Colors.grey[200],
valueColor:
AlwaysStoppedAnimation<Color>(Colors.blue),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
),
),
SizedBox(height: 20),
Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.grey),
)
],
);
} else {
return ElevatedButton(
onPressed: controller.isUpdateAvailable.value
? null
: controller.checkUpdate,
style: ElevatedButton.styleFrom(
backgroundColor: controller.isUpdateAvailable.value
? Colors.grey
: Colors.blue,
foregroundColor: Colors.white,
padding:
EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: Text(
controller.isUpdateAvailable.value ? '已是最新版本' : '立即更新',
style: TextStyle(fontSize: 16),
),
);
}
}),
],
// 状态显示区域
Obx(() {
if (controller.isFetchingDeviceInfo.value) {
return _buildLoadingIndicator();
}
if (controller.errorMessage.value.isNotEmpty) {
return _buildErrorInfo();
}
return _buildUpdateButton();
}),
],
),
),
),
),
),
);
}
// 组件化构建方法
Widget _buildDeviceIcon() => CircleAvatar(
radius: 60,
backgroundColor: Colors.blue[100],
child: Icon(Icons.phone_iphone, size: 60, color: Colors.blue),
);
Widget _buildVersionInfo() => Column(
children: [
Text('当前版本', style: TextStyle(fontSize: 18, color: Colors.grey)),
SizedBox(height: 8),
Text('v${controller.currentVersion}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue)),
],
);
Widget _buildLoadingIndicator() => Column(
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text('正在连接设备...', style: TextStyle(color: Colors.grey))
],
);
Widget _buildErrorInfo() => Column(
children: [
Icon(Icons.error_outline, color: Colors.red, size: 40),
SizedBox(height: 10),
Text(controller.errorMessage.value,
style: TextStyle(color: Colors.red)),
SizedBox(height: 20),
ElevatedButton(
onPressed: controller.retryConnection,
child: Text('重试连接'),
)
],
);
Widget _buildUpdateButton() {
return controller.isUpdating.value
? _buildProgressIndicator()
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: controller.isUpdateAvailable.value
? Colors.blue
: Colors.grey,
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: controller.isUpdateAvailable.value
? controller.startUpdate
: null,
child: Text(
controller.isUpdateAvailable.value ? '立即更新' : '已是最新版本',
style: TextStyle(fontSize: 16, color: Colors.white),
),
);
}
Widget _buildProgressIndicator() => Column(
children: [
SizedBox(
width: 200,
child: LinearProgressIndicator(
value: controller.downloadProgress.value,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
),
),
SizedBox(height: 20),
Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.grey))
],
);
}

138
lib/modules/pairing/controllers/pairing_controller.dart

@ -10,13 +10,16 @@ import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice;
import '../../../data/services/ble_manager.dart';
import 'package:agent_service/agent_service.dart';
import 'package:ble_service/ble_service.dart';
/// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程
class PairingController extends GetxController {
static PairingController get to => Get.find();
//是否需要连接耳机
final isneedearphone = false;
// 状态标志
final isLoading = false.obs;
// 权限是否已授予
final _hasPermissions = false.obs;
bool get hasPermissions => _hasPermissions.value;
@ -25,22 +28,22 @@ class PairingController extends GetxController {
final _bluetoothState = 'unknown'.obs;
String get bluetoothState => _bluetoothState.value;
bool get isBluetoothOn => _bluetoothState.value == 'on';
// 状态描述(用于UI显示)
final statusDescription = 'initializing'.tr.obs;
// 是否正在跳转到主页
bool _isNavigatingToHome = false;
// 是否正在执行Companion关联流程
bool _isAssociating = false;
// 当前连接设备信息
BluetoothDevice? get currentDevice => _bluetoothService.currentDevice;
// 是否已连接设备
bool get isDeviceConnected => currentDevice != null;
// 是否已完成Companion关联
final _companionAssociated = false.obs;
bool get isCompanionAssociated => _companionAssociated.value;
@ -48,10 +51,10 @@ class PairingController extends GetxController {
// 服务状态监听
Worker? _bluetoothStateWorker;
Worker? _deviceWorker;
// 蓝牙服务
final _bluetoothService = Get.find<BluetoothManager>();
// BLE服务
final _bleService = BleService();
@ -96,7 +99,7 @@ class PairingController extends GetxController {
// 刷新已连接设备
statusDescription.value = 'searchingDevice'.tr;
await refreshConnectedDevices();
// 检查是否已存在Companion设备关联,但不要重复调用关联流程
await _checkExistingCompanionAssociation();
} catch (e) {
@ -107,16 +110,16 @@ class PairingController extends GetxController {
_updateStatusDescription();
}
}
/// 检查是否已存在Companion设备关联
Future<void> _checkExistingCompanionAssociation() async {
// 只在Android平台执行
if (!Platform.isAndroid) return;
try {
final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) return;
final pairedMac = await _bleService.getPairedMacAddress();
if (pairedMac != null) {
Logger.info('发现已关联的Companion设备: $pairedMac');
@ -165,18 +168,18 @@ class PairingController extends GetxController {
Logger.warning('权限 ${permission.toString()} 未被授予: $status');
}
});
_hasPermissions.value = allGranted;
return allGranted;
}
// 辅助方法获取Android SDK版本
Future<int> _getAndroidSdkVersion() async {
if (Platform.isAndroid) {
// 这里需要原生代码支持来获取确切的SDK版本,
// 或者使用 device_info_plus 插件
// 暂时返回一个较高值以触发新权限请求
return 31; // 假设运行在需要新权限的设备上
// 这里需要原生代码支持来获取确切的SDK版本,
// 或者使用 device_info_plus 插件
// 暂时返回一个较高值以触发新权限请求
return 31; // 假设运行在需要新权限的设备上
}
return 0;
}
@ -184,38 +187,40 @@ class PairingController extends GetxController {
/// 设置服务状态监听
void _setupServiceStateListeners() {
// 监听蓝牙服务的蓝牙状态变化
_bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) {
_bluetoothStateWorker =
ever(_bluetoothService.bluetoothState.obs, (String state) {
_bluetoothState.value = state;
_updateStatusDescription();
// 如果蓝牙关闭,可能需要更新UI或提示用户
if (state == 'off') {
Logger.info('蓝牙已关闭');
}
});
// 监听当前设备变化 - 使用公开的currentDeviceRx字段
_deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) {
_deviceWorker =
ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) {
if (device != null && !_isNavigatingToHome) {
Logger.info('配对页面发现设备已连接: ${device.name}');
// 当经典蓝牙设备连接成功,尝试进行Companion关联
_handleDeviceConnected();
}
_updateStatusDescription();
});
if (!isneedearphone) {
//不需要耳机
_handleDeviceConnected();
}
}
/// 处理设备连接成功事件
Future<void> _handleDeviceConnected() async {
if (!isDeviceConnected || _isNavigatingToHome) return;
if ((!isDeviceConnected && isneedearphone) || _isNavigatingToHome) return;
// 如果已经完成关联,直接跳转
if (_companionAssociated.value) {
if (isneedearphone && _companionAssociated.value) {
_autoNavigateToHome();
return;
}
// 启动关联流程
_startCompanionAssociation();
}
@ -226,7 +231,7 @@ class PairingController extends GetxController {
statusDescription.value = 'permissionDenied'.tr;
return;
}
if (bluetoothState == 'off') {
statusDescription.value = 'bluetoothTurnedOff'.tr;
return;
@ -234,16 +239,12 @@ class PairingController extends GetxController {
if (isDeviceConnected) {
if (Platform.isAndroid && !isCompanionAssociated && _isAssociating) {
statusDescription.value = '${'associatingDevice'.tr} ${currentDevice!.name}';
statusDescription.value =
'${'associatingDevice'.tr} ${currentDevice!.name}';
} else if (isCompanionAssociated) {
statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}';
} else {
// 检查是否为DeepSound耳机
if (!currentDevice!.name.contains("DeapSound")) {
statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr;
} else {
statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}';
}
statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}';
}
return;
}
@ -262,18 +263,18 @@ class PairingController extends GetxController {
statusDescription.value = 'permissionDenied'.tr;
return;
}
try {
isLoading.value = true;
statusDescription.value = 'searchingDevice'.tr;
// 使用蓝牙服务刷新连接
await _bluetoothService.refreshDeviceConnection();
// 检查连接状态
if (isDeviceConnected) {
Logger.info('刷新发现设备已连接: ${currentDevice!.name}');
// 当发现设备已连接,统一由_handleDeviceConnected处理
_handleDeviceConnected();
} else {
@ -287,18 +288,20 @@ class PairingController extends GetxController {
_updateStatusDescription();
}
}
/// 启动Companion设备关联流程
Future<void> _startCompanionAssociation() async {
// 如果不是Android平台,已经跳转到首页,已经完成关联或正在进行关联,直接返回
if (!Platform.isAndroid || _isNavigatingToHome || _companionAssociated.value || _isAssociating) {
if (!Platform.isAndroid ||
_isNavigatingToHome ||
_companionAssociated.value ||
_isAssociating) {
return;
}
// 设置关联状态标志
_isAssociating = true;
_updateStatusDescription();
try {
// 检查是否支持Companion Device功能
final isSupported = await _bleService.isCompanionSupported();
@ -314,7 +317,7 @@ class PairingController extends GetxController {
// 不清除关联,因为MAC地址匹配
return;
}
// 检查是否已有配对的MAC地址
final pairedMac = await _bleService.getPairedMacAddress();
if (pairedMac != null) {
@ -326,8 +329,8 @@ class PairingController extends GetxController {
Logger.info('已关联的Companion设备与当前连接的设备一致');
_companionAssociated.value = true;
await _bleService.registerBackgroundScan();
_autoNavigateToHome();
return;
// _autoNavigateToHome();
// return;
} else {
// MAC地址不匹配
Logger.warning('未找到已关联的DeepSound耳机(MAC地址不匹配)');
@ -337,26 +340,24 @@ class PairingController extends GetxController {
return;
}
}
// 开始关联流程
if (currentDevice != null) {
Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}');
final success = await _bleService.startDeviceAssociation(
var success = await _bleService.startDeviceAssociation(
deviceNamePattern: currentDevice!.name,
macAddress: currentDevice!.address,
);
if (success) {
Logger.info('Companion设备关联成功');
_companionAssociated.value = true;
// 注册后台扫描,确保应用被杀死后仍能被唤醒
await _bleService.registerBackgroundScan(macAddress: currentDevice!.address);
await _bleService.registerBackgroundScan(
macAddress: currentDevice!.address);
// 启动BLE后台服务(可选)
await _startAgentService();
// 关联和后台扫描都成功后,再跳转
_autoNavigateToHome();
} else {
@ -364,6 +365,12 @@ class PairingController extends GetxController {
// 关联失败,但仍然可以跳转到主页(用户体验考虑)
_autoNavigateToHome();
}
} else if (!isneedearphone) {
// 启动BLE后台服务(可选)
await _startAgentService();
// 关联和后台扫描都成功后,再跳转
_autoNavigateToHome();
}
} catch (e) {
Logger.error('Companion设备关联过程发生错误: $e');
@ -379,10 +386,10 @@ class PairingController extends GetxController {
/// 自动跳转到主页
Future<void> _autoNavigateToHome() async {
if (_isNavigatingToHome) return;
// 如果没有连接,不进行跳转
if (!isDeviceConnected) return;
// 在Android平台上,如果支持Companion且仍在关联中,不跳转
if (Platform.isAndroid) {
final isSupported = await _bleService.isCompanionSupported();
@ -391,25 +398,24 @@ class PairingController extends GetxController {
return;
}
}
_isNavigatingToHome = true;
Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页');
Future.delayed(const Duration(milliseconds: 500), () {
Get.offAllNamed(Routes.home);
});
}
/// 启动BLE后台服务
Future<void> _startAgentService() async {
try {
if (!isDeviceConnected) return;
if (isneedearphone && !isDeviceConnected) return;
// 启动BLE服务,无需传递配置参数,将从.env读取
final result = await BleManager.to.startAgentService();
if (result) {
Logger.info('BLE后台服务启动成功');
} else {
@ -419,12 +425,12 @@ class PairingController extends GetxController {
Logger.error('启动BLE后台服务发生错误: $e');
}
}
/// 停止BLE后台服务
Future<void> _stopAgentService() async {
try {
final result = await BleManager.to.stopAgentService();
if (result) {
Logger.info('BLE后台服务停止成功');
} else {
@ -451,7 +457,7 @@ class PairingController extends GetxController {
Logger.error('打开设置失败: $e');
}
}
/// 打开应用设置
Future<void> openAppSettings() async {
await AppSettings.openAppSettings();

1
local_plugins/agent_service/android/build.gradle.kts

@ -48,4 +48,5 @@ dependencies {
add("compileOnly", project(":deepvoice_utils"))
add("compileOnly", project(":chat_storage"))
add("compileOnly", project(":ble_service"))
add("compileOnly", project(":music_service"))
}

70
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -683,6 +683,42 @@ object AgentService : CoroutineScope {
"result" to functionCallResult.toString(),
))
FileLogger.d(TAG, "mcp调用结果: $functionCallResult")
val metaStr = functionCallResult.optString("meta")
if (metaStr.isNotEmpty()) {
metadata = metaStr;
val meta = JSONObject(metaStr)
FileLogger.d(TAG, "mcp调用结果: $metaStr")
if (meta.has("card_music")) { //音乐卡片
val cardMusic = meta.getJSONObject("card_music")
FileLogger.d(TAG, "检查到音乐卡片: $cardMusic")
val musicUrl = cardMusic.optString("url", "")
val name = cardMusic.optString("name", "")
val sgener = cardMusic.optString("sgener", "")
processMusicPlay(musicUrl, name, sgener)
}else if (meta.has("card_musiclist")) { //音乐列表
val cardMusiclist = meta.getJSONObject("card_musiclist")
val musics = cardMusiclist.optJSONArray("musics")
if (musics != null) {
val playlist = mutableListOf<Map<String, String>>()
for (i in 0 until musics.length()) {
val item = musics.optJSONObject(i) ?: continue
val url = item.optString("url", "")
val name = item.optString("name", "")
val sgener = item.optString("sgener", "")
if (url.isNotEmpty()) {
playlist.add(mapOf("url" to url, "name" to name, "sgener" to sgener))
}
}
if (playlist.isNotEmpty()) {
processMusicPlayList(playlist)
} else {
FileLogger.w(TAG, "card_musiclist 中没有有效的音乐条目")
}
} else {
FileLogger.w(TAG, "card_musiclist.musics 不是一个数组")
}
}
}
}
}
)
@ -1025,4 +1061,36 @@ object AgentService : CoroutineScope {
}
}
}
}
/**
* 处理音乐播放
* 将ai返回的音乐连接 在后台启动播放
*
* @param musicUrl 图片文件路径
* @param name 歌曲名称,默认为空
* @param singer 歌手,默认为false
* @return 是否成功开始处理
*/
fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){
// 在其他 Service、BroadcastReceiver 或 Application 中调用
FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl")
MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl)
}
/**
* 处理音乐播放列表
* 将ai返回的音乐连接 在后台启动播放
*
* @param musicUrl 图片文件路径
* @param name 歌曲名称,默认为空
* @param singer 歌手,默认为false
* @return 是否成功开始处理
*/
fun processMusicPlayList(playlist:List<Map<String,String>>){
// 在其他 Service、BroadcastReceiver 或 Application 中调用
val urls = playlist.mapNotNull { it["url"] }.distinct()
FileLogger.i(TAG, "启动音乐服务 播放音乐列表 $urls")
MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls = urls)
}
}

60
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -473,7 +473,7 @@ object BleService {
}
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) {
val data = c.value ?: return
// 根据特征UUID区分处理
@ -1017,9 +1017,11 @@ object BleService {
stopOpusStreamDecoding() // 停止Opus解码流
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()) // 或其他默认声道
byteArrayOf(
BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
) // 或其他默认声道
)
}
@ -1027,45 +1029,54 @@ object BleService {
* 控制编解码 - 打开解码
*/
fun openDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...")
FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()))
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
}
/**
* 控制编解码 - 打开解码
*/
fun openA2DPDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...")
FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()))
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
}
/**
@ -1087,11 +1098,11 @@ object BleService {
channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
// packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
)
// 注意:此方法通常是控制设备端开始编码并发送数据。
// App侧通常是打开解码器(openDecoder)来接收和处理这些数据。
@ -1178,7 +1189,6 @@ object BleService {
return bluetoothAdapter
}
// ======================================================================================================
// 资源清理

10
local_plugins/ble_service/ios/Classes/BleService.swift

@ -353,6 +353,11 @@ actor BleService {
func getDeviceVersion() -> Bool {
return sendCommand(BleConst.CMD_GET_VERSION)
}
///获取产品ID
/// - Returns: 是否成功发送请求
func getProductId() -> Bool {
return sendCommand(BleConst.CMD_GET_PRODUCT_ID)
}
/// 获取电池信息
/// - Returns: 是否成功发送请求
@ -371,6 +376,11 @@ actor BleService {
func openDecoder() -> Bool {
return sendCodecControl(BleConst.CODEC_CONTROL_DECODE_ON)
}
/// 打开解码器
/// - Returns: 是否成功发送请求
func openA2DPDecoder() -> Bool {
return sendCodecControl(BleConst.CODEC_CONTROL_A2DP_PLAY)
}
/// 关闭编解码器
/// - Returns: 是否成功发送请求

8
local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift

@ -73,12 +73,18 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin, BleDelegate {
case "getDeviceVersion":
result(bleService.getDeviceVersion())
case "getProductId":
result(bleService.getProductId())
case "openEncoder":
result(bleService.openEncoder())
case "openDecoder":
result(bleService.openDecoder())
case "openA2DPDecoder":
result(bleService.openA2DPDecoder())
case "closeCodec":
result(bleService.closeCodec())

11
local_plugins/ble_service/lib/ble_service.dart

@ -83,6 +83,17 @@ class BleService {
}
}
/// 获取产品ID
Future<bool> getProductId() async {
try {
final result = await _methodChannel.invokeMethod<bool>('getProductId');
return result ?? false;
} catch (e) {
print('获取设备版本失败: $e');
return false;
}
}
/// 获取电池信息
Future<bool> getBatteryInfo() async {
try {

1
local_plugins/music_service/android/build.gradle

@ -67,4 +67,5 @@ android {
dependencies {
implementation project(':deepvoice_utils')
implementation("androidx.media:media:1.6.0")
}

9
local_plugins/music_service/android/src/main/AndroidManifest.xml

@ -3,7 +3,6 @@
<!-- 基础前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 14 及以上需要声明具体的前台服务类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
@ -13,5 +12,13 @@
android:exported="false"
android:enabled="true"
android:foregroundServiceType="mediaPlayback" />
<!-- 用于接收媒体按钮(耳机按钮、蓝牙播放控制) -->
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>

87
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

@ -9,6 +9,8 @@ import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger
import java.util.concurrent.atomic.AtomicBoolean
@ -28,7 +30,7 @@ class MusicService : Service() {
fun isPlaying(): Boolean = currentStatus
fun getCurrentUrl(): String? = currentUrlInternal
}
private lateinit var mediaSession: MediaSessionCompat
private var mediaPlayer: MediaPlayer? = null
private val playlist = mutableListOf<String>() // 可选:初始化为预置歌曲
private var currentIndex = 0
@ -38,9 +40,38 @@ class MusicService : Service() {
FileLogger.init(this)
FileLogger.i(TAG, "MusicService 正在创建...")
isRunning.set(true)
Log.i(TAG, "MusicService Created")
mediaSession = MediaSessionCompat(this, TAG).apply {
setCallback(object : MediaSessionCompat.Callback() {
override fun onPlay() {
resume()
}
override fun onPause() {
pause()
}
override fun onSkipToNext() {
if (currentIndex + 1 < playlist.size) {
currentIndex++
play(playlist[currentIndex])
}
}
override fun onSkipToPrevious() {
if (currentIndex - 1 >= 0) {
currentIndex--
play(playlist[currentIndex])
}
}
override fun onStop() {
stopSelf()
}
})
isActive = true
}
startForeground(NOTIFICATION_ID, createNotification("等待播放..."))
isRunning.set(true)
Log.i(TAG, "MusicService Created")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -173,15 +204,14 @@ class MusicService : Service() {
manager.createNotificationChannel(channel)
}
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val pendingIntent = intent?.let {
PendingIntent.getActivity(
this, 0, it,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}
// 修复 pendingIntent:打开 App 主 Activity
val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
val pendingIntent = PendingIntent.getActivity(
this,
0,
activityIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("DeepSound")
@ -189,6 +219,39 @@ class MusicService : Service() {
.setSmallIcon(android.R.drawable.ic_media_play)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
.setShowActionsInCompactView(0, 1, 2)
)
.addAction(
NotificationCompat.Action(
android.R.drawable.ic_media_previous, "上一曲",
androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
)
)
)
.addAction(
NotificationCompat.Action(
if (currentStatus) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play,
if (currentStatus) "暂停" else "播放",
androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
PlaybackStateCompat.ACTION_PLAY_PAUSE
)
)
)
.addAction(
NotificationCompat.Action(
android.R.drawable.ic_media_next, "下一曲",
androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
this,
PlaybackStateCompat.ACTION_SKIP_TO_NEXT
)
)
)
.build()
}

25
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger
import com.yunqiinnovation.music_service.MusicServiceStarter.startServiceWithCommand
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
@ -24,22 +25,33 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
FileLogger.d(TAG, "Method call: ${call.method}")
val intent = Intent(context, MusicService::class.java)
when (call.method) {
"play" -> {
val url = call.argument<String>("url")
MusicServiceStarter.startServiceWithCommand(context, "play", url)
startServiceWithCommand(context, "play", url)
result.success(null)
}
"playlist" -> {
val urls = call.argument<List<String>>("urls")
MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls)
result.success(null)
}
"pause" -> {
startServiceWithCommand(context, "pause")
result.success(null)
}
"resume" -> {
startServiceWithCommand(context, "resume")
result.success(null)
}
"stop" -> {
startServiceWithCommand(context, "stop")
result.success(null)
}
"pause" -> MusicServiceStarter.startServiceWithCommand(context, "pause")
"resume" -> MusicServiceStarter.startServiceWithCommand(context, "resume")
"stop" -> MusicServiceStarter.startServiceWithCommand(context, "stop")
"setVolume" -> {
val volume = call.argument<Double>("volume")?.toFloat()
MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume)
startServiceWithCommand(context, "setVolume", volume = volume)
result.success(null)
}
"getStatus" -> {
val isPlaying = MusicService.isPlaying()
@ -52,7 +64,6 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler {
}
else -> result.notImplemented()
}
result.success(null)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}

Loading…
Cancel
Save