Browse Source

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

newdev_shunjiawei
lxm 1 year ago
parent
commit
f9a72d6fc2
  1. 28
      lib/data/models/user_Info.dart
  2. 9
      lib/data/services/network/api.dart
  3. 14
      lib/data/services/speech_impl/azure_tts_service.dart
  4. 8
      lib/data/services/speech_impl/voice_clone_tts_service.dart
  5. 39
      lib/data/services/tts_service.dart
  6. 129
      lib/modules/pairing/controllers/pairing_controller.dart
  7. 51
      lib/modules/pairing/views/pairing_view.dart
  8. 73
      lib/modules/pairing/views/pairing_view_new.dart
  9. 3
      lib/modules/settings/views/settings_view.dart
  10. 6
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  11. 6
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

28
lib/data/models/user_Info.dart

@ -14,14 +14,15 @@ class User {
String name; String name;
String uid; String uid;
String avatar; String avatar;
String deviceid;
String language; String language;
String token; String token;
String mail; String mail;
String phone; String phone;
String phonemac; String phonemac;
String password; String password;
int membershipLevel = 0; //添加会员等级字段,0表示普通用户 int viplv = 0; //添加会员等级字段,0表示普通用户
String earphonemac;
String earphoneauthcode;
// final Int64 lastbettime; // final Int64 lastbettime;
// final Int64 createtime; // final Int64 createtime;
// final String wxopenid; // final String wxopenid;
@ -33,14 +34,15 @@ class User {
required this.name, required this.name,
required this.uid, required this.uid,
required this.avatar, required this.avatar,
required this.deviceid,
required this.language, required this.language,
required this.token, required this.token,
required this.mail, required this.mail,
required this.phone, required this.phone,
required this.phonemac, required this.phonemac,
required this.password, required this.password,
this.membershipLevel = 0, // 添加默认值 required this.viplv, // 添加默认值
required this.earphonemac,
required this.earphoneauthcode,
// required this.lastbettime, // required this.lastbettime,
// required this.createtime, // required this.createtime,
// required this.wxopenid, // required this.wxopenid,
@ -60,14 +62,16 @@ class User {
name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值 name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值
uid: (userData?['uid'] as String?) ?? '', uid: (userData?['uid'] as String?) ?? '',
avatar: (userData?['avatar'] as String?) ?? '', avatar: (userData?['avatar'] as String?) ?? '',
deviceid: (userData?['deviceid'] as String?) ?? '',
language: (userData?['language'] as String?) ?? 'en', language: (userData?['language'] as String?) ?? 'en',
mail: (userData?['mail'] as String?) ?? '', mail: (userData?['mail'] as String?) ?? '',
phone: (userData?['phone'] as String?) ?? '', phone: (userData?['phone'] as String?) ?? '',
phonemac: (userData?['phonemac'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '', password: (userData?['password'] as String?) ?? '',
membershipLevel: (userData?['membershipLevel'] as int?) ?? 0, // 提供默认值 viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonemac: (userData?['earphonemac'] as String?) ?? '',
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '',
// lastbettime: _parseTimestamp(userData['lastbettime']), // lastbettime: _parseTimestamp(userData['lastbettime']),
// createtime: _parseTimestamp(userData['createtime']), // createtime: _parseTimestamp(userData['createtime']),
// wxopenid: (userData?['wxopenid'] as String?) ?? '', // wxopenid: (userData?['wxopenid'] as String?) ?? '',
@ -85,13 +89,16 @@ class User {
name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值 name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值
uid: (userData?['uid'] as String?) ?? '', uid: (userData?['uid'] as String?) ?? '',
avatar: (userData?['avatar'] as String?) ?? '', avatar: (userData?['avatar'] as String?) ?? '',
deviceid: (userData?['deviceid'] as String?) ?? '',
language: (userData?['language'] as String?) ?? 'en', language: (userData?['language'] as String?) ?? 'en',
mail: (userData?['mail'] as String?) ?? '', mail: (userData?['mail'] as String?) ?? '',
phone: (userData?['phone'] as String?) ?? '', phone: (userData?['phone'] as String?) ?? '',
phonemac: (userData?['phonemac'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '', password: (userData?['password'] as String?) ?? '',
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonemac: (userData?['earphonemac'] as String?) ?? '',
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '',
// lastbettime: _parseTimestamp(userData['lastbettime']), // lastbettime: _parseTimestamp(userData['lastbettime']),
// createtime: _parseTimestamp(userData['createtime']), // createtime: _parseTimestamp(userData['createtime']),
// wxopenid: (userData?['wxopenid'] as String?) ?? '', // wxopenid: (userData?['wxopenid'] as String?) ?? '',
@ -106,14 +113,17 @@ class User {
'name': name, 'name': name,
'uid': uid, 'uid': uid,
'avatar': avatar, 'avatar': avatar,
'deviceid': deviceid,
'language': language, 'language': language,
'token': token, 'token': token,
'mail': mail, 'mail': mail,
'phone': phone, 'phone': phone,
'phonemac': phonemac, 'phonemac': phonemac,
'password': password, 'password': password,
'membershipLevel': membershipLevel, 'viplv': viplv,
'earphonemac': earphonemac,
'earphoneauthcode': earphoneauthcode,
// 'lastbettime': lastbettime, // 'lastbettime': lastbettime,
// 'createtime': createtime, // 'createtime': createtime,
// 'wxopenid': wxopenid, // 'wxopenid': wxopenid,

9
lib/data/services/network/api.dart

@ -126,4 +126,13 @@ class Api {
params: params, params: params,
); );
} }
//绑定设备
static bindearphone([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/user_bindearphone',
params: params,
);
}
} }

14
lib/data/services/speech_impl/azure_tts_service.dart

@ -413,4 +413,16 @@ class AzureTtsService extends GetxService implements TtsService {
Logger.error('释放资源失败: ${e.toString()}'); Logger.error('释放资源失败: ${e.toString()}');
} }
} }
}
@override
Future<bool> setAudioOutputDevice() async {
try {
// 开始合成,传递参数
final result = await _channel.invokeMethod('setAudioOutputDevice');
return result;
} catch (e) {
Logger.error('音频输出设备类型失败: ${e.toString()}');
return false;
}
}
}

8
lib/data/services/speech_impl/voice_clone_tts_service.dart

@ -390,4 +390,10 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
Logger.error('释放资源失败: ${e.toString()}'); Logger.error('释放资源失败: ${e.toString()}');
} }
} }
}
@override
Future<bool> setAudioOutputDevice() {
// TODO: implement setAudioOutputDevice
throw UnimplementedError();
}
}

39
lib/data/services/tts_service.dart

@ -2,52 +2,51 @@ import 'dart:async';
/// 语音合成服务接口 /// 语音合成服务接口
abstract class TtsService { abstract class TtsService {
/// 当前选择的语音 /// 当前选择的语音
String get currentVoice; String get currentVoice;
/// 当前是否正在播放 /// 当前是否正在播放
bool get isSpeaking; bool get isSpeaking;
/// TTS事件流,用于监听语音合成事件 /// TTS事件流,用于监听语音合成事件
Stream<TtsEvent> get onEvent; Stream<TtsEvent> get onEvent;
/// 初始化语音合成服务 /// 初始化语音合成服务
Future<bool> initialize({ Future<bool> initialize({
List<String>? supportedLanguages, List<String>? supportedLanguages,
}); });
/// 设置语音 /// 设置语音
Future<bool> setVoice(String voiceName); Future<bool> setVoice(String voiceName);
/// 单次播放文本,完成后返回 /// 单次播放文本,完成后返回
Future<bool> speakOnce(String text); Future<bool> speakOnce(String text);
/// 将文本加入播放队列,支持连续播放多段文本 /// 将文本加入播放队列,支持连续播放多段文本
Future<bool> speakStream(String text); Future<bool> speakStream(String text);
/// 播放队列中剩余文本 /// 播放队列中剩余文本
Future<bool> flushStream(); Future<bool> flushStream();
/// 停止播放 /// 停止播放
Future<bool> stop(); Future<bool> stop();
/// 释放资源 /// 释放资源
Future<void> dispose(); Future<void> dispose();
Future<bool> setAudioOutputDevice();
} }
/// TTS事件类型 /// TTS事件类型
enum TtsEventType { enum TtsEventType {
/// 开始播放 /// 开始播放
started, started,
/// 播放完成 /// 播放完成
completed, completed,
/// 播放取消 /// 播放取消
canceled, canceled,
/// 播放错误 /// 播放错误
error, error,
} }
@ -56,15 +55,15 @@ enum TtsEventType {
class TtsEvent { class TtsEvent {
/// 事件类型 /// 事件类型
final TtsEventType type; final TtsEventType type;
/// 错误信息(仅在 error 类型中有效) /// 错误信息(仅在 error 类型中有效)
final String error; final String error;
TtsEvent({ TtsEvent({
required this.type, required this.type,
this.error = '', this.error = '',
}); });
/// 创建错误事件的快捷构造函数 /// 创建错误事件的快捷构造函数
factory TtsEvent.error(String errorMessage) { factory TtsEvent.error(String errorMessage) {
return TtsEvent( return TtsEvent(
@ -72,12 +71,12 @@ class TtsEvent {
error: errorMessage, error: errorMessage,
); );
} }
/// 检查是否为错误 /// 检查是否为错误
bool get isError => type == TtsEventType.error; bool get isError => type == TtsEventType.error;
@override @override
String toString() { String toString() {
return 'TtsEvent{type: $type, error: $error}'; return 'TtsEvent{type: $type, error: $error}';
} }
} }

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

@ -1,4 +1,7 @@
import 'package:app_settings/app_settings.dart'; import 'package:app_settings/app_settings.dart';
import 'package:deep_voice/data/models/user_Info.dart';
import 'package:deep_voice/data/services/network/api.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'dart:async'; import 'dart:async';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
@ -15,9 +18,10 @@ enum PairingState {
checking, // 检查中(蓝牙、权限等) checking, // 检查中(蓝牙、权限等)
waitingClassic, // 等待经典蓝牙连接 waitingClassic, // 等待经典蓝牙连接
verifying, // 验证设备 verifying, // 验证设备
manualbinding, //手动绑定设备
associating, // 正在进行BLE设备关联 associating, // 正在进行BLE设备关联
waitingBle, // 等待BLE连接 waitingBle, //等待BLE连接
completed, // 配对完成 completed, //配对完成
error, // 错误状态 error, // 错误状态
} }
@ -31,6 +35,7 @@ enum ErrorType {
deviceBound, // 设备已被绑定 deviceBound, // 设备已被绑定
associationFailed, // 关联失败 associationFailed, // 关联失败
bleTimeout, // BLE连接超时 bleTimeout, // BLE连接超时
authnFailed, // 授权失败
networkError, // 网络错误 networkError, // 网络错误
} }
@ -53,6 +58,7 @@ class PairingController extends GetxController {
final isLoading = false.obs; final isLoading = false.obs;
final showConnectButton = false.obs; final showConnectButton = false.obs;
final showRetryButton = false.obs; final showRetryButton = false.obs;
final showAutoCodeInput = false.obs;
final showSkipButton = false.obs; final showSkipButton = false.obs;
final connectButtonText = '连接设备'.obs; final connectButtonText = '连接设备'.obs;
final retryButtonText = '重试'.obs; final retryButtonText = '重试'.obs;
@ -68,6 +74,7 @@ class PairingController extends GetxController {
// ========== 当前设备信息 ========== // ========== 当前设备信息 ==========
BluetoothDevice? _currentClassicDevice; BluetoothDevice? _currentClassicDevice;
String? _verifiedDeviceAddress; String? _verifiedDeviceAddress;
TextEditingController codeInputController = TextEditingController();
// Public getter for view access // Public getter for view access
BluetoothDevice? get currentDevice => _currentClassicDevice; BluetoothDevice? get currentDevice => _currentClassicDevice;
@ -236,6 +243,7 @@ class PairingController extends GetxController {
showConnectButton.value = false; showConnectButton.value = false;
showRetryButton.value = false; showRetryButton.value = false;
showSkipButton.value = false; showSkipButton.value = false;
showAutoCodeInput.value = false;
switch (state) { switch (state) {
case PairingState.idle: case PairingState.idle:
@ -276,7 +284,12 @@ class PairingController extends GetxController {
_startBleConnection(); _startBleConnection();
}); });
break; break;
case PairingState.manualbinding:
statusText.value = '需要手动版定授权码...';
isLoading.value = false;
showAutoCodeInput.value = true;
showSkipButton.value = true;
break;
case PairingState.completed: case PairingState.completed:
statusText.value = '配对成功!'; statusText.value = '配对成功!';
isLoading.value = false; isLoading.value = false;
@ -306,6 +319,10 @@ class PairingController extends GetxController {
showRetryButton.value = true; showRetryButton.value = true;
retryButtonText.value = '重新关联'; retryButtonText.value = '重新关联';
break; break;
case ErrorType.authnFailed:
showRetryButton.value = true;
retryButtonText.value = '重新授权';
break;
default: default:
showRetryButton.value = true; showRetryButton.value = true;
retryButtonText.value = '重试'; retryButtonText.value = '重试';
@ -393,44 +410,48 @@ class PairingController extends GetxController {
Future<void> _verifyDevice(BluetoothDevice device) async { Future<void> _verifyDevice(BluetoothDevice device) async {
try { try {
Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})'); Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})');
// TODO: 实现API验证逻辑 // TODO: 实现API验证逻辑
// 暂时模拟验证通过 // 暂时模拟验证通过
await Future.delayed(const Duration(seconds: 2)); // await Future.delayed(const Duration(seconds: 2));;
if (await authbindingDevice()) {
// 检查设备名称是否包含 DeepSound 或 DeapSound // 检查设备名称是否包含 DeepSound 或 DeapSound
final deviceName = device.name ?? ''; final deviceName = device.name ?? '';
if (deviceName.contains('DeepSound') || if (deviceName.contains('DeepSound') ||
deviceName.contains('DeapSound')) { deviceName.contains('DeapSound')) {
Logger.i(_tag, '设备验证通过'); Logger.i(_tag, '设备验证通过');
_verifiedDeviceAddress = device.address; _verifiedDeviceAddress = device.address;
// 检查是否已有BLE关联 // 检查是否已有BLE关联
final pairedMacAddress = await _bleManager.getPairedMacAddress(); final pairedMacAddress = await _bleManager.getPairedMacAddress();
Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress'); Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress');
if (pairedMacAddress != null && if (pairedMacAddress != null &&
pairedMacAddress.toUpperCase() == device.address.toUpperCase()) { pairedMacAddress.toUpperCase() == device.address.toUpperCase()) {
// 已有关联,检查BLE连接状态 // 已有关联,检查BLE连接状态
Logger.i(_tag, '设备已关联,检查BLE连接状态'); Logger.i(_tag, '设备已关联,检查BLE连接状态');
final currentBleState = await _bleManager.getConnectionState(); final currentBleState = await _bleManager.getConnectionState();
Logger.i(_tag, '验证时BLE状态: $currentBleState'); Logger.i(_tag, '验证时BLE状态: $currentBleState');
if (currentBleState == 2) { if (currentBleState == 2) {
// BleConnectionState.connected // BleConnectionState.connected
Logger.i(_tag, 'BLE已连接,直接完成配对'); Logger.i(_tag, 'BLE已连接,直接完成配对');
_transition(PairingState.completed); _transition(PairingState.completed);
} else {
Logger.i(_tag, 'BLE未连接,等待BLE连接');
_transition(PairingState.waitingBle);
}
} else { } else {
Logger.i(_tag, 'BLE未连接,等待BLE连接'); // 没有关联或关联的是其他设备,需要进行关联
_transition(PairingState.waitingBle); Logger.i(_tag, '需要进行BLE设备关联');
_transition(PairingState.associating);
} }
} else { } else {
// 没有关联或关联的是其他设备,需要进行关联 // _setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备');
Logger.i(_tag, '需要进行BLE设备关联'); _transition(PairingState.completed);
_transition(PairingState.associating);
} }
} else { } else {
_setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备'); //验证失败 进入手动授权
_transition(PairingState.manualbinding);
} }
} catch (e) { } catch (e) {
Logger.e(_tag, '设备验证异常: $e'); Logger.e(_tag, '设备验证异常: $e');
@ -504,6 +525,46 @@ class PairingController extends GetxController {
} }
} }
//自动绑定设备
Future<bool> authbindingDevice() async {
if (User.instance.earphonemac == currentDevice?.address) {
// 已经绑定
return true;
}
// 调用BLE管理器的自动绑定方法
final response = await Api.bindearphone({
"earphonemac": currentDevice?.address,
"earphoneauthcode": "",
});
Logger.i(_tag, '手动授权结果 ${response}');
if (response != null) {
Logger.i(_tag, '设备自动绑定成功');
User.instance.earphonemac = currentDevice?.address ?? '';
return true;
} else {
Logger.e(_tag, '手动绑定失败!');
return false;
}
}
/// 用户点击授权码输入框
void onAutoCodeInputTap() async {
// 调用BLE管理器的自动绑定方法
final response = await Api.bindearphone({
"earphonemac": currentDevice?.address,
"earphoneauthcode": codeInputController.text.trim(),
});
Logger.i(_tag, '手动授权结果 ${response}');
if (response != null) {
_transition(PairingState.verifying);
_verifyDevice(_currentClassicDevice!);
} else {
Logger.e(_tag, '手动绑定失败!');
_setError(ErrorType.authnFailed, '设备授权失败,请检查授权码');
}
}
/// 导航到主页 /// 导航到主页
void _navigateToHome() { void _navigateToHome() {
Future.delayed(const Duration(seconds: 1), () async { Future.delayed(const Duration(seconds: 1), () async {

51
lib/modules/pairing/views/pairing_view.dart

@ -336,6 +336,8 @@ class PairingView extends GetView<PairingController> {
return '扫描超时,请确保设备在附近'; return '扫描超时,请确保设备在附近';
case ErrorType.networkError: case ErrorType.networkError:
return '网络错误,请检查网络连接'; return '网络错误,请检查网络连接';
case ErrorType.authnFailed:
return '授权失败,请检查授权码是否正确';
default: default:
return '配对失败,请重试'; return '配对失败,请重试';
} }
@ -433,7 +435,7 @@ class PairingView extends GetView<PairingController> {
final showConnectButton = controller.showConnectButton.value; final showConnectButton = controller.showConnectButton.value;
final showRetryButton = controller.showRetryButton.value; final showRetryButton = controller.showRetryButton.value;
final showSkipButton = controller.showSkipButton.value; final showSkipButton = controller.showSkipButton.value;
final showAutoCodeInput = controller.showAutoCodeInput.value;
return Container( return Container(
width: double.infinity, width: double.infinity,
child: Column( child: Column(
@ -490,6 +492,51 @@ class PairingView extends GetView<PairingController> {
), ),
), ),
), ),
if (showAutoCodeInput)
Column(
children: [
// 输入框
SizedBox(
width: double.infinity,
height: 48.h,
child: TextField(
controller: controller
.codeInputController, // 你可以在 Controller 中声明这个
decoration: InputDecoration(
hintText: '请输入代码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w),
),
),
),
SizedBox(height: 12.h),
SizedBox(
width: double.infinity,
height: 48.h,
child: ElevatedButton(
onPressed: controller.onAutoCodeInputTap,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(Get.context!).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
elevation: 0,
),
child: Text(
'授权绑定',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
],
),
// 自动输入代码按钮
// 跳过按钮 // 跳过按钮
if (showSkipButton) ...[ if (showSkipButton) ...[
@ -510,11 +557,9 @@ class PairingView extends GetView<PairingController> {
), ),
), ),
], ],
], ],
), ),
); );
}); });
} }
} }

73
lib/modules/pairing/views/pairing_view_new.dart

@ -21,17 +21,17 @@ class PairingView extends GetView<PairingController> {
children: [ children: [
// 顶部标题 // 顶部标题
_buildHeader(isDarkMode), _buildHeader(isDarkMode),
// 中间内容 // 中间内容
Expanded( Expanded(
child: Center( child: Center(
child: Obx(() => _buildContent(isDarkMode)), child: Obx(() => _buildContent(isDarkMode)),
), ),
), ),
// 底部按钮 // 底部按钮
Obx(() => _buildButtons(isDarkMode)), Obx(() => _buildButtons(isDarkMode)),
SizedBox(height: 20.h), SizedBox(height: 20.h),
], ],
), ),
@ -69,15 +69,15 @@ class PairingView extends GetView<PairingController> {
Widget _buildContent(bool isDarkMode) { Widget _buildContent(bool isDarkMode) {
final state = controller.currentState; final state = controller.currentState;
final isLoading = controller.isLoading.value; final isLoading = controller.isLoading.value;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// 状态图标 // 状态图标
_buildStateIcon(state, isLoading, isDarkMode), _buildStateIcon(state, isLoading, isDarkMode),
SizedBox(height: 40.h), SizedBox(height: 40.h),
// 状态文本 // 状态文本
Text( Text(
controller.statusText.value, controller.statusText.value,
@ -87,7 +87,7 @@ class PairingView extends GetView<PairingController> {
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
// 错误详情 // 错误详情
if (state == PairingState.error) ...[ if (state == PairingState.error) ...[
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -132,48 +132,51 @@ class PairingView extends GetView<PairingController> {
), ),
); );
} }
IconData iconData; IconData iconData;
Color iconColor; Color iconColor;
switch (state) { switch (state) {
case PairingState.idle: case PairingState.idle:
case PairingState.checking: case PairingState.checking:
iconData = Icons.bluetooth_searching; iconData = Icons.bluetooth_searching;
iconColor = Colors.blue; iconColor = Colors.blue;
break; break;
case PairingState.waitingClassic: case PairingState.waitingClassic:
iconData = Icons.bluetooth; iconData = Icons.bluetooth;
iconColor = Colors.grey; iconColor = Colors.grey;
break; break;
case PairingState.verifying: case PairingState.verifying:
iconData = Icons.verified_user; iconData = Icons.verified_user;
iconColor = Colors.orange; iconColor = Colors.orange;
break; break;
case PairingState.associating: case PairingState.associating:
iconData = Icons.link; iconData = Icons.link;
iconColor = Colors.blue; iconColor = Colors.blue;
break; break;
case PairingState.waitingBle: case PairingState.waitingBle:
iconData = Icons.sync; iconData = Icons.sync;
iconColor = Colors.blue; iconColor = Colors.blue;
break; break;
case PairingState.manualbinding:
iconData = Icons.blind;
iconColor = Colors.blue;
break;
case PairingState.completed: case PairingState.completed:
iconData = Icons.check_circle; iconData = Icons.check_circle;
iconColor = Colors.green; iconColor = Colors.green;
break; break;
case PairingState.error: case PairingState.error:
iconData = Icons.error; iconData = Icons.error;
iconColor = Colors.red; iconColor = Colors.red;
break; break;
} }
return Container( return Container(
width: 100.w, width: 100.w,
height: 100.w, height: 100.w,
@ -193,8 +196,9 @@ class PairingView extends GetView<PairingController> {
Widget _buildButtons(bool isDarkMode) { Widget _buildButtons(bool isDarkMode) {
final showConnectButton = controller.showConnectButton.value; final showConnectButton = controller.showConnectButton.value;
final showRetryButton = controller.showRetryButton.value; final showRetryButton = controller.showRetryButton.value;
final showAutoCodeInput = controller.showAutoCodeInput.value;
final showSkipButton = controller.showSkipButton.value; final showSkipButton = controller.showSkipButton.value;
return Column( return Column(
children: [ children: [
// 主要操作按钮 // 主要操作按钮
@ -204,16 +208,40 @@ class PairingView extends GetView<PairingController> {
controller.onConnectTap, controller.onConnectTap,
isDarkMode, isDarkMode,
), ),
if (showRetryButton) if (showRetryButton)
_buildPrimaryButton( _buildPrimaryButton(
'重试', '重试',
controller.onRetryTap, controller.onRetryTap,
isDarkMode, isDarkMode,
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
if (showAutoCodeInput)
// 自动输入代码按钮
SizedBox(
width: double.infinity,
height: 48.h,
child: ElevatedButton(
onPressed: controller.onAutoCodeInputTap,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(Get.context!).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
elevation: 0,
),
child: Text(
'自动输入代码',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
// 跳过按钮 // 跳过按钮
if (showSkipButton) if (showSkipButton)
TextButton( TextButton(
@ -231,7 +259,8 @@ class PairingView extends GetView<PairingController> {
} }
/// 构建主要按钮 /// 构建主要按钮
Widget _buildPrimaryButton(String text, VoidCallback onPressed, bool isDarkMode) { Widget _buildPrimaryButton(
String text, VoidCallback onPressed, bool isDarkMode) {
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
height: 48.h, height: 48.h,
@ -255,4 +284,4 @@ class PairingView extends GetView<PairingController> {
), ),
); );
} }
} }

3
lib/modules/settings/views/settings_view.dart

@ -200,8 +200,7 @@ class SettingsView extends GetView<SettingsController> {
2: 'proUser', //'专业用户' 2: 'proUser', //'专业用户'
}; };
final membershipString = membershipMap[ final membershipString = membershipMap[
controller controller.userInfo.value.viplv] ??
.userInfo.value.membershipLevel] ??
'Unknown'; 'Unknown';
return Text( return Text(
membershipString, membershipString,

6
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -15,7 +15,7 @@ import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType import com.deep_voice.speech.tts.TtsEventType
import com.yunqiinnovation.ble_service.BleService import com.yunqiinnovation.ble_service.BleService
import com.deep_voice.speech.tts.AudioOutputDevice
/** AzureSpeechPlugin */ /** AzureSpeechPlugin */
class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
private val tag = "AzureSpeechPlugin" private val tag = "AzureSpeechPlugin"
@ -501,6 +501,10 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
azureTtsHelper.release() azureTtsHelper.release()
result.success(true) result.success(true)
} }
"setAudioOutputDevice" -> {
azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER)
result.success(true)
}
else -> { else -> {
result.notImplemented() result.notImplemented()

6
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

@ -223,7 +223,11 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
} }
AudioOutputDevice.SPEAKER -> { AudioOutputDevice.SPEAKER -> {
// 强制使用扬声器 // 强制使用扬声器
manager.mode = AudioManager.MODE_NORMAL /* MODE_NORMAL 默认媒体模式 ❌ 不能强制扬声器(会被耳机覆盖)
MODE_IN_COMMUNICATION VoIP 通信模式 ✅ 可以强制使用扬声器或听筒
MODE_IN_CALL 电话模式(仅限系统) ✅ 更强但风险大,不推荐使用
*/
manager.mode = AudioManager.MODE_IN_COMMUNICATION
manager.isSpeakerphoneOn = true manager.isSpeakerphoneOn = true
} }
AudioOutputDevice.HEADPHONES -> { AudioOutputDevice.HEADPHONES -> {

Loading…
Cancel
Save