moyoung_glasses_ble_plugin 1.0.0
moyoung_glasses_ble_plugin: ^1.0.0 copied to clipboard
A comprehensive Flutter plugin for MoYoung smart glasses, providing 50+ features including: • Bluetooth communication and device management • Camera control (photo/video recording) • Audio recording a [...]
example/lib/main.dart
import 'dart:io';
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:moyoung_glasses_ble_plugin/moyoung_glasses_ble.dart';
import 'package:moyoung_glasses_ble_plugin/impl/moyoung_glasses_beans.dart';
import 'package:moyoung_glasses_ble_plugin/impl/channel_names.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'utils/toast_util.dart';
import 'event_manager.dart';
import 'glasses_scan_page.dart';
import 'mac_address_cache.dart';
import 'package:permission_handler/permission_handler.dart';
import 'l10n/app_strings.dart';
import 'utils/locale_manager.dart';
// 翻译音频数据Bean(临时定义,实际应该在插件中定义)
class TranslateAudioDataBean {
final int frameIndex;
final Uint8List data;
TranslateAudioDataBean({
required this.frameIndex,
required this.data,
});
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final MoYoungGlassesBle _glassesPlugin = MoYoungGlassesBle();
final _streamSubscriptions = <StreamSubscription<dynamic>>[];
// 语言管理
Locale _locale = const Locale('zh', 'CN');
String _permissionTxt = AppStrings.requestPermission;
String _connectionStatus = AppStrings.disconnected;
String _batteryLevel = AppStrings.unknown;
bool _isCharging = false;
String _deviceVersion = AppStrings.unknown;
String _jlVersion = AppStrings.unknown;
String _qzVersion = AppStrings.unknown;
// 存储实际值,用于语言切换时重新格式化
int _actualBatteryLevel = 0;
String _actualDeviceVersion = '';
String _actualJlVersion = '';
String _actualQzVersion = '';
String _actualRunningStatus = '';
String _actualLanguageStatus = '';
String _actualDeviceUUIDStatus = '';
String _actualVoiceWakeupStatus = '';
String _actualAudioTalkState = '';
String _actualBluetoothState = '';
String _githashVersion = AppStrings.unknown;
String _fileBaseUrl = AppStrings.waitingToReceive;
bool isQuit = false;
bool _isConnected = false;
String _audioDataStatus = AppStrings.noAudioData;
String _aiImageDataStatus = AppStrings.noAiImageData;
String _translateAudioStatus = AppStrings.noTranslationAudioData;
String _pcmAudioStatus = AppStrings.noPcmAudioData;
String _audioRecordStatus = AppStrings.clickToGet; // 录音状态
String _languageStatus = AppStrings.clickToGet; // 语言设置状态
String _deviceUUIDStatus = AppStrings.clickToGet; // 设备UUID状态
String _voiceWakeupStatus = AppStrings.clickToGet; // 语音唤醒状态
String _runningStatus = AppStrings.clickToGet; // 运行状态
String _otaStatus = AppStrings.waitingOtaStatus; // OTA升级状态
String _actionResultStatus = AppStrings.waitingActionResult; // Wifi操作结果状态
String _sdkLogStatus = AppStrings.waitingSdkLog; // SDK日志状态
String? _cachedMacAddress; // 缓存的 MAC 地址
String? _cachedDeviceName; // 缓存的设备名称
String _audioTalkState = AppStrings.notSet; // 音频控制状态
int _selectedAudioAction = 1; // 选择的音频动作类型
String _bluetoothState = AppStrings.unknown; // 蓝牙状态
int _selectedAIStatus = 0; // 选择的AI回复状态类型
bool _hasQueriedBattery = false; // 是否已查询过电池信息
int _selectedFrameRate = 0; // 选择的录像帧率
int _selectedMaxDuration = 60; // 选择的录像最大时长
int _selectedVoiceWakeupAction = 0; // 选择的语音唤醒操作:0=开启(TypeOn),1=关闭(TypeOff)
@override
void initState() {
super.initState();
_loadLocale();
_loadCachedMacAddress();
_subscribeToStreams();
_checkInitialBluetoothState();
// 延迟检查初始连接状态,确保事件流监听已启动
// 使用渐进式检查,一旦成功就停止后续检查
Timer(const Duration(milliseconds: 500), () {
_checkInitialConnectionState();
});
Timer(const Duration(seconds: 2), () {
// 只有在第一次检查失败且仍未连接时才进行第二次检查
if (!_hasCheckedInitialConnection || !_isConnected) {
_checkInitialConnectionState();
}
});
}
/// 更新状态文本(语言切换时调用,只更新显示文本,不改变数据源)
void _updateStatusTexts() {
setState(() {
_permissionTxt = AppStrings.requestPermissionStatus;
// 只更新连接状态的显示文本,数据源 _isConnected 保持不变
_connectionStatus = _isConnected ? AppStrings.connected : AppStrings.disconnected;
// 只根据实际数据源重新格式化显示文本,不改变数据源
_batteryLevel = _actualBatteryLevel > 0
? AppStrings.batteryDisplay("$_actualBatteryLevel", _isCharging)
: AppStrings.unknownStatus;
_deviceVersion = _actualDeviceVersion.isNotEmpty
? "${AppStrings.deviceVersion}: $_actualDeviceVersion"
: AppStrings.unknownStatus;
_jlVersion = _actualJlVersion.isNotEmpty
? AppStrings.jlVersion(_actualJlVersion)
: AppStrings.unknownStatus;
_qzVersion = _actualQzVersion.isNotEmpty
? AppStrings.qzVersion(_actualQzVersion)
: AppStrings.unknownStatus;
// 根据实际状态重新设置显示文本
_runningStatus = _actualRunningStatus.isNotEmpty
? _actualRunningStatus
: AppStrings.clickToGet;
_languageStatus = _actualLanguageStatus.isNotEmpty
? _actualLanguageStatus
: AppStrings.clickToGet;
_deviceUUIDStatus = _actualDeviceUUIDStatus.isNotEmpty
? _actualDeviceUUIDStatus
: AppStrings.clickToGet;
_voiceWakeupStatus = _actualVoiceWakeupStatus.isNotEmpty
? _actualVoiceWakeupStatus
: AppStrings.clickToGet;
_audioTalkState = _actualAudioTalkState.isNotEmpty
? _actualAudioTalkState
: AppStrings.notSet;
_bluetoothState = _actualBluetoothState.isNotEmpty
? _actualBluetoothState
: AppStrings.unknownStatus;
// 这些状态文本根据当前语言重新设置
_githashVersion = AppStrings.unknownStatus;
_fileBaseUrl = AppStrings.waitingToReceive;
_audioDataStatus = AppStrings.noAudioData;
_aiImageDataStatus = AppStrings.noAiImageData;
_translateAudioStatus = AppStrings.noTranslationAudio;
_pcmAudioStatus = AppStrings.noPcmAudio;
_audioRecordStatus = AppStrings.clickToGet;
_otaStatus = AppStrings.waitingOtaStatus;
_actionResultStatus = AppStrings.waitingActionResult;
_sdkLogStatus = AppStrings.waitingSdkLog;
});
}
/// 加载语言设置
Future<void> _loadLocale() async {
final savedLocale = LocaleManager.getSavedLocale();
setState(() {
_locale = savedLocale;
AppStrings.setLocale(savedLocale);
});
// 语言加载完成后,初始化显示文本
_initializeStatusTexts();
}
/// 初始化状态文本(只在语言加载后调用一次)
void _initializeStatusTexts() {
setState(() {
// 设置初始显示文本,这些会在数据更新时被替换
_permissionTxt = AppStrings.requestPermissionStatus;
_connectionStatus = AppStrings.disconnected;
_batteryLevel = AppStrings.unknownStatus;
_deviceVersion = AppStrings.unknownStatus;
_jlVersion = AppStrings.unknownStatus;
_qzVersion = AppStrings.unknownStatus;
_githashVersion = AppStrings.unknownStatus;
_fileBaseUrl = AppStrings.waitingToReceive;
_audioDataStatus = AppStrings.noAudioData;
_aiImageDataStatus = AppStrings.noAiImageData;
_translateAudioStatus = AppStrings.noTranslationAudio;
_pcmAudioStatus = AppStrings.noPcmAudio;
_audioRecordStatus = AppStrings.clickToGet;
_languageStatus = AppStrings.clickToGet;
_deviceUUIDStatus = AppStrings.clickToGet;
_voiceWakeupStatus = AppStrings.clickToGet;
_runningStatus = AppStrings.clickToGet;
_otaStatus = AppStrings.waitingOtaStatus;
_actionResultStatus = AppStrings.waitingActionResult;
_sdkLogStatus = AppStrings.waitingSdkLog;
_audioTalkState = AppStrings.notSet;
_bluetoothState = AppStrings.unknownStatus;
});
}
/// 切换语言
Future<void> _changeLanguage(Locale locale) async {
await LocaleManager.saveLocale(locale);
setState(() {
_locale = locale;
AppStrings.setLocale(locale);
});
// 更新所有状态文本
_updateStatusTexts();
}
/// 加载缓存的 MAC 地址
Future<void> _loadCachedMacAddress() async {
final mac = await MacAddressCache.getCachedMac();
final name = await MacAddressCache.getCachedDeviceName();
setState(() {
_cachedMacAddress = mac;
_cachedDeviceName = name;
});
if (mac != null && name != null) {
print(AppStrings.loadCachedDevice(name!, mac!));
}
}
void _subscribeToStreams() {
// 监听连接状态
_streamSubscriptions.add(
_glassesPlugin.connStateEveStm.listen((ConnectStateBean event) {
debugPrint('main.dart received connection state event: ${event.connectState}');
setState(() {
// connectState: 0=断开, 1=连接中, 2=已连接, 3=断开中
_isConnected = event.connectState == 2;
_connectionStatus = _getConnectionStateText(event.connectState);
debugPrint('main.dart updated state: _isConnected=$_isConnected, _connectionStatus=$_connectionStatus');
});
// 连接成功时发送通知并更新信息
if (event.connectState == 2) {
EventManager().emit(EventNames.connectionSuccess, {
'isConnected': true,
});
debugPrint('Sent connection success notification');
// 连接成功后更新基本信息(只查询一次)
if (!_hasQueriedBattery) {
// 延迟一下,避免与初始连接检查冲突
Timer(const Duration(milliseconds: 100), () async {
if (_isConnected && !_hasQueriedBattery) {
await _loadCachedMacAddress();
_queryBattery();
_queryDeviceVersion();
_hasQueriedBattery = true;
}
});
}
}
// 断开连接时清除状态
else if (event.connectState == 0) {
_hasQueriedBattery = false;
setState(() {
_cachedMacAddress = null;
_cachedDeviceName = null;
});
debugPrint('Device disconnected');
}
}),
);
// 监听Wi-Fi状态文件BaseUrl
_streamSubscriptions.add(
_glassesPlugin.fileBaseUrlEveStm.listen((String baseUrl) {
setState(() {
_fileBaseUrl = baseUrl;
});
_showToast(AppStrings.receivedFileBaseUrl(baseUrl));
}),
);
// 监听蓝牙状态
_streamSubscriptions.add(
_glassesPlugin.bluetoothStateEveStm.listen((int state) {
String stateText = AppStrings.unknownState;
switch (state) {
case 0:
stateText = AppStrings.unknownConnectionState;
break;
case 1:
stateText = AppStrings.connectingStatus;
break;
case 2:
stateText = AppStrings.connectedStatus;
break;
case 3:
stateText = AppStrings.disconnecting;
break;
case 4:
stateText = AppStrings.disconnectedDone;
break;
default:
stateText = AppStrings.unknownConnectionState;
}
debugPrint('Bluetooth state: $stateText (value: $state)');
setState(() {
_actualBluetoothState = stateText;
_bluetoothState = stateText;
});
}),
);
// 监听音频数据流
_streamSubscriptions.add(
_glassesPlugin.audioDataEveStm.listen((Map<String, dynamic> data) {
debugPrint('Received audio data: $data');
setState(() {
if (data['hasData'] == true) {
int frameIndex = data['frameIndex'] ?? 0;
int size = data['size'] ?? 0;
_audioDataStatus = AppStrings.receivedAudioData(frameIndex, size);
} else {
_audioDataStatus = AppStrings.noAudioData;
}
});
}),
);
// 监听ACK错误
_streamSubscriptions.add(
_glassesPlugin.ackErrorEveStm.listen((Map<String, dynamic> error) {
debugPrint('Received ACK error: $error');
int code = error['code'] ?? -1;
String message = error['message'] ?? AppStrings.unknownError;
bool shouldShowToast = true;
// 如果是语音唤醒相关的错误,更新状态显示
if (_voiceWakeupStatus == AppStrings.gettingStatusWithDots) {
setState(() {
_actualVoiceWakeupStatus = AppStrings.notSupportedOrErrorStatus;
_voiceWakeupStatus = AppStrings.notSupportedOrErrorStatus;
});
// 语音唤醒错误不显示Toast,避免重复提示
shouldShowToast = false;
}
// 如果是运行状态相关的错误,更新状态显示
if (_runningStatus == AppStrings.gettingStatusWithDots) {
setState(() {
_actualRunningStatus = AppStrings.notSupportedOrErrorStatus;
_runningStatus = AppStrings.notSupportedOrErrorStatus;
});
// 运行状态错误不显示Toast,避免重复提示
shouldShowToast = false;
}
// 只在非功能查询错误时显示Toast
if (shouldShowToast) {
_showToast(AppStrings.errorMessage(code.toString(), message));
}
}),
);
// 监听音频控制状态(SDK的receiveAudioState回调)
_streamSubscriptions.add(
_glassesPlugin.audioTalkStateEveStm.listen((int state) {
String stateText = AppStrings.unknownState;
switch (state) {
case 0:
stateText = AppStrings.stopAudio;
break;
case 1:
stateText = AppStrings.startAudio;
break;
case 2:
stateText = AppStrings.cancelAudio;
break;
case 3:
stateText = AppStrings.dnsStreamStart;
break;
case 4:
stateText = AppStrings.dnsStreamPause;
break;
case 5:
stateText = AppStrings.dnsStreamStop;
break;
case 6:
stateText = AppStrings.normalStreamStart;
break;
case 7:
stateText = AppStrings.normalStreamPause;
break;
case 8:
stateText = AppStrings.normalStreamStop;
break;
}
setState(() {
_actualAudioTalkState = stateText;
_audioTalkState = stateText;
});
debugPrint('Audio control state: $stateText (value: $state)');
}),
);
// 监听音频状态
_streamSubscriptions.add(
_glassesPlugin.audioStateEveStm.listen((AudioStateBean event) {
debugPrint('Received audio state: state=${event.state}, frameIndex=${event.frameIndex}, dataSize=${event.dataSize}');
setState(() {
if (event.hasData == true && event.dataSize != null) {
// 收到音频数据
debugPrint('Received audio data: frame=${event.frameIndex}, size=${event.dataSize} bytes');
_audioDataStatus = AppStrings.receivedAudioData(event.frameIndex ?? 0, event.dataSize ?? 0);
} else {
// 只是状态变化
switch (event.state) {
case 0:
_audioDataStatus = AppStrings.audioIdle;
break;
case 1:
_audioDataStatus = AppStrings.recording;
break;
case 2:
_audioDataStatus = AppStrings.audioPaused;
break;
default:
_audioDataStatus = AppStrings.unknownAudioStatus;
}
}
});
}),
);
// 监听AI识别图片数据
_streamSubscriptions.add(
_glassesPlugin.aiImageDataEveStm.listen((data) {
debugPrint('Received AI image data: size=${data.length} bytes');
setState(() {
_aiImageDataStatus = AppStrings.receivedImageData(data.length);
});
}),
);
// 监听翻译音频数据
_streamSubscriptions.add(
_glassesPlugin.translateAudioEveStm.listen((Map<String, dynamic> data) {
debugPrint('Received translation audio data: $data');
int frameIndex = data['frameIndex'] ?? 0;
List<int> audioData = List<int>.from(data['data'] ?? []);
setState(() {
_translateAudioStatus = AppStrings.receivedAudioData(frameIndex, audioData.length);
});
}),
);
// 监听PCM音频数据 - 仅在iOS平台上监听
if (Platform.isIOS) {
_streamSubscriptions.add(
_glassesPlugin.pcmAudioEveStm.listen((Map<String, dynamic> data) {
debugPrint('Received PCM audio data: $data');
int frameIndex = data['frameIndex'] ?? 0;
List<int> audioData = List<int>.from(data['data'] ?? []);
setState(() {
_pcmAudioStatus = AppStrings.receivedAudioData(frameIndex, audioData.length);
});
}),
);
}
// 监听OTA升级状态
_streamSubscriptions.add(
_glassesPlugin.otaStateEveStm.listen((Map<String, dynamic> data) {
debugPrint('Received OTA upgrade event: $data');
int type = data['type'] ?? 0;
int progress = data['progress'] ?? 0;
String typeText = type == 0 ? 'Preparing' : type == 1 ? 'Upgrading' : type == 2 ? 'Complete' : 'Failed';
setState(() {
_otaStatus = "$typeText (${progress}%)";
});
// 只在重要状态变化时显示Toast
if (type == 2 || type == 3) {
_showToast(typeText);
}
}),
);
// 监听Wifi操作结果
_streamSubscriptions.add(
_glassesPlugin.actionResultEveStm.listen((Map<String, dynamic> data) {
debugPrint('Received action result: $data');
int code = data['code'] ?? -1;
String msg = data['msg'] ?? '';
String statusText = '';
// 根据官方Demo,只确认 code=0 表示成功
if (code == 0) {
statusText = msg.isEmpty ? 'Connected' : 'Success: $msg';
} else {
// 其他状态码显示原始信息
statusText = msg.isEmpty ? 'Operation result(code: $code)' : 'Result: $msg (code: $code)';
}
setState(() {
_actionResultStatus = statusText;
});
_showToast(statusText);
}),
);
// 监听SDK日志
_streamSubscriptions.add(
_glassesPlugin.sdkLogEveStm.listen((String log) {
debugPrint('Received log message: $log');
// 更新状态显示,只显示最新的日志
setState(() {
// 截取日志,避免太长
String displayLog = log.length > 50 ? '${log.substring(0, 47)}...' : log;
_sdkLogStatus = displayLog;
});
// 重要日志仍然显示Toast
if (log.contains('ERROR') || log.contains('WARN')) {
_showToast(AppStrings.logMessage(log));
}
}),
);
// 监听运行状态
_streamSubscriptions.add(
_glassesPlugin.runningStatusEveStm.listen((Map<String, dynamic> status) {
debugPrint('Received running status event: $status');
String actualStatus = status['status'] ?? 'Unknown status';
setState(() {
_actualRunningStatus = actualStatus;
_runningStatus = actualStatus;
});
}),
);
// 监听电池信息
_streamSubscriptions.add(
_glassesPlugin.batteryEveStm.listen((Map<String, dynamic> batteryInfo) {
debugPrint('Received battery info event: $batteryInfo');
if (batteryInfo['error'] != null) {
debugPrint('Battery info error: ${batteryInfo['error']}');
return;
}
int batteryLevel = 0;
bool isCharging = false;
// 解析电池数据
if (batteryInfo['battery'] != null) {
batteryLevel = int.tryParse(batteryInfo['battery'].toString()) ?? 0;
}
if (batteryInfo['charging'] != null) {
isCharging = batteryInfo['charging'].toString().toLowerCase() == 'true';
}
// 只在电量变化时更新UI(避免频繁刷新)
if (_actualBatteryLevel != batteryLevel || _isCharging != isCharging) {
setState(() {
_actualBatteryLevel = batteryLevel;
_batteryLevel = AppStrings.batteryDisplay("$batteryLevel", isCharging);
_isCharging = isCharging;
});
debugPrint('Battery info updated: level=$batteryLevel%, charging=$isCharging');
}
}),
);
}
String _getConnectionStateText(int state) {
switch (state) {
case 2:
return AppStrings.connected;
case 1:
return AppStrings.connectingStatus;
case 0:
return AppStrings.disconnected;
case 3:
return AppStrings.disconnecting;
default:
return AppStrings.unknownConnectionState;
}
}
@override
void dispose() {
for (final subscription in _streamSubscriptions) {
subscription.cancel();
}
super.dispose();
}
/// 退出app并且断开连接
Future<bool> exitAppWithDisconnect() {
if (!isQuit) {
_showToast(AppStrings.pressAgainToExit);
isQuit = true;
return Future.value(false);
}
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
return Future.value(true);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
locale: _locale,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en', 'US'),
],
home: Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: Text(AppStrings.title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
systemOverlayStyle: SystemUiOverlayStyle.dark,
actions: [
PopupMenuButton<Locale>(
icon: const Icon(Icons.language),
onSelected: _changeLanguage,
itemBuilder: (context) => [
PopupMenuItem(
value: const Locale('zh', 'CN'),
child: Text(AppStrings.chinese),
),
PopupMenuItem(
value: const Locale('en', 'US'),
child: Text(AppStrings.english),
),
],
),
],
),
body: WillPopScope(
onWillPop: () => exitAppWithDisconnect(),
child: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
// 状态卡片
_buildStatusCard(),
const SizedBox(height: 20),
// 基础功能
_buildSectionCard(
title: AppStrings.deviceControl,
icon: Icons.settings,
children: [
_buildApiButton(
AppStrings.bluetoothCheck,
Icons.security,
requestPermissions,
subtitle: _permissionTxt,
),
Builder(
builder: (context) => _buildApiButton(
AppStrings.scanDevice,
Icons.bluetooth_searching,
() => _navigateToScanPage(context),
subtitle: AppStrings.scanAndConnect,
),
),
_buildApiButton(
AppStrings.bluetoothStatus,
Icons.bluetooth,
() {},
subtitle: _bluetoothState,
enabled: false,
),
_buildApiButton(
AppStrings.showConnectionStatusTitle,
Icons.info,
_updateConnectionState,
subtitle: AppStrings.showConnectionStatus,
),
_buildApiButton(
AppStrings.reconnectDevice,
Icons.link,
_reconnectDevice,
subtitle: AppStrings.reconnectLastDevice,
enabled: !_isConnected, // 只有在未连接时才显示
),
_buildApiButton(
AppStrings.disconnectAndRemove,
Icons.link_off,
_removeDevice,
subtitle: AppStrings.removeAndDisconnect,
enabled: _isConnected,
),
],
),
const SizedBox(height: 20),
// 设备控制
_buildSectionCard(
title: AppStrings.deviceControl,
icon: Icons.phone_android,
children: [
_buildApiButton(
AppStrings.syncTime,
Icons.access_time,
() => _syncTime(),
subtitle: AppStrings.syncDeviceTime,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.batteryLevel,
Icons.battery_charging_full,
_queryBattery,
subtitle: _batteryLevel,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.sendLanguageSettings,
Icons.language,
_sendLanguage,
subtitle: AppStrings.setGlassesLanguage,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.restartGlasses,
Icons.restart_alt,
() => _restartDevice(),
subtitle: AppStrings.restartDevice,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.factoryReset,
Icons.restore,
_resetDevice,
subtitle: AppStrings.factoryResetGlasses,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.shutdownDevice,
Icons.power_off,
_shutdownDevice,
subtitle: AppStrings.shutdownGlasses,
enabled: _isConnected,
),
],
),
const SizedBox(height: 20),
// 眼镜功能
_buildSectionCard(
title: AppStrings.glassesFeatures,
icon: Icons.camera,
children: [
_buildApiButton(
AppStrings.takePhoto,
Icons.camera_alt,
_takePhoto,
subtitle: AppStrings.controlPhoto,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.startVideo,
Icons.videocam,
_startVideo,
subtitle: AppStrings.startVideoFunction,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.stopVideo,
Icons.videocam_off,
_stopVideo,
subtitle: AppStrings.stopVideoFunction,
enabled: _isConnected,
),
],
),
const SizedBox(height: 20),
// 音频控制功能
_buildSectionCard(
title: AppStrings.audioControlFunction,
icon: Icons.mic,
children: [
// 音频状态显示
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.purple.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.purple.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.graphic_eq, color: Colors.purple[600], size: 20),
const SizedBox(width: 8),
Text(
AppStrings.audioDataStatus,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.purple[600],
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: Text(
_audioDataStatus,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 13,
color: Colors.purple[700],
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
),
),
const SizedBox(height: 12),
// 音频状态选择区域
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.audioControlStatus,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
flex: 2,
child: DropdownButton<int>(
value: _selectedAudioAction,
isExpanded: true,
underline: Container(
height: 1,
color: Colors.grey[400],
),
items: [
DropdownMenuItem(value: 1, child: Text(AppStrings.startAudio)),
DropdownMenuItem(value: 2, child: Text(AppStrings.cancelAudio)),
DropdownMenuItem(value: 3, child: Text(AppStrings.startDnsStream)),
DropdownMenuItem(value: 4, child: Text(AppStrings.pauseDnsStream)),
DropdownMenuItem(value: 5, child: Text(AppStrings.stopDnsStream)),
DropdownMenuItem(value: 6, child: Text(AppStrings.startNormalStream)),
DropdownMenuItem(value: 7, child: Text(AppStrings.pauseNormalStream)),
DropdownMenuItem(value: 8, child: Text(AppStrings.stopNormalStream)),
],
onChanged: _isConnected ? (value) {
setState(() {
_selectedAudioAction = value!;
});
} : null,
),
),
const SizedBox(width: 12),
Expanded(
flex: 1,
child: ElevatedButton.icon(
onPressed: _isConnected ? _startAudio : null,
icon: const Icon(Icons.send, size: 18),
label: Text(AppStrings.send),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
],
),
],
),
),
_buildApiButton(
AppStrings.stopAudioStatus,
Icons.mic_off,
_stopAudio,
subtitle: AppStrings.stopAudioControlState,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.audioControlStatus,
Icons.volume_up,
() {},
subtitle: _audioTalkState,
enabled: false,
),
],
),
const SizedBox(height: 20),
// AI 功能
_buildSectionCard(
title: AppStrings.aiFunctions,
icon: Icons.psychology,
children: [
// AI 识别图片数据状态显示
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.image, color: Colors.blue[600], size: 20),
const SizedBox(width: 8),
Text(
AppStrings.aiImageDataStatus,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.blue[600],
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: Text(
_aiImageDataStatus,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 13,
color: Colors.blue[700],
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
),
),
const SizedBox(height: 8),
// 翻译音频数据状态显示
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.translate, color: Colors.green[600], size: 20),
const SizedBox(width: 8),
Text(
AppStrings.stopAudioStatus,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.green[600],
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: Text(
_translateAudioStatus,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 13,
color: Colors.green[700],
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
),
),
const SizedBox(height: 8),
// PCM 音频数据状态显示
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.mic, color: Colors.orange[600], size: 20),
const SizedBox(width: 8),
Text(
AppStrings.pcmAudioStatus,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.orange[600],
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: Text(
_pcmAudioStatus,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 13,
color: Colors.orange[700],
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.chat, color: Colors.grey[600]),
const SizedBox(width: 12),
Expanded(
child: DropdownButton<int>(
value: _selectedAIStatus,
isExpanded: true,
items: [
DropdownMenuItem(value: 0, child: Text(AppStrings.startAiReply)),
DropdownMenuItem(value: 1, child: Text(AppStrings.completeAiReply)),
DropdownMenuItem(value: 2, child: Text(AppStrings.interruptAiReply)),
],
onChanged: _isConnected ? (value) {
setState(() {
_selectedAIStatus = value!;
});
} : null,
),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: _isConnected ? _setAIReplyStatus : null,
child: Text(AppStrings.send),
),
],
),
),
_buildApiButton(
AppStrings.exitVoice,
Icons.stop_circle,
_exitAIReply,
subtitle: AppStrings.exitVoiceReplyState,
enabled: _isConnected,
),
],
),
const SizedBox(height: 20),
// Wi-Fi 功能(文件同步)
_buildSectionCard(
title: AppStrings.wifiFileSync,
icon: Icons.wifi,
children: [
_buildApiButton(
AppStrings.enterFileSyncMode,
Icons.sync,
_setFileSyncModeEnter,
subtitle: AppStrings.enableFileTransfer,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.exitFileSyncMode,
Icons.sync_disabled,
_setFileSyncModeExit,
subtitle: AppStrings.disableFileTransfer,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.getDefaultVideoParams,
Icons.settings,
_queryVideoConfig,
subtitle: AppStrings.getVideoDefaultParams,
enabled: _isConnected,
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.setVideoDefaultParams,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[800],
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.frameRate,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
DropdownButton<int>(
value: _selectedFrameRate,
isExpanded: true,
items: [
DropdownMenuItem(value: 0, child: Text(AppStrings.notSet)),
DropdownMenuItem(value: 15, child: Text(AppStrings.fps15)),
DropdownMenuItem(value: 24, child: Text(AppStrings.fps24)),
DropdownMenuItem(value: 30, child: Text(AppStrings.fps30)),
DropdownMenuItem(value: 60, child: Text(AppStrings.fps60)),
],
onChanged: _isConnected ? (value) {
setState(() {
_selectedFrameRate = value!;
});
} : null,
),
],
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.maxDuration,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
DropdownButton<int>(
value: _selectedMaxDuration,
isExpanded: true,
items: [
DropdownMenuItem(value: 30, child: Text(AppStrings.seconds30)),
DropdownMenuItem(value: 60, child: Text(AppStrings.seconds60)),
DropdownMenuItem(value: 120, child: Text(AppStrings.seconds120)),
DropdownMenuItem(value: 300, child: Text(AppStrings.seconds300)),
],
onChanged: _isConnected ? (value) {
setState(() {
_selectedMaxDuration = value!;
});
} : null,
),
],
),
),
],
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isConnected ? _setVideoConfig : null,
child: Text(AppStrings.applySettings),
),
),
],
),
),
// 操作结果显示
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.wifi_outlined, size: 20, color: Colors.blue[700]),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
AppStrings.wifiOperationResult,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(width: 8),
Text(
AppStrings.codeZeroSuccess,
style: TextStyle(
fontSize: 10,
color: Colors.grey[500],
),
),
],
),
const SizedBox(height: 4),
Text(
_actionResultStatus,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.blue[700],
),
),
],
),
),
],
),
),
],
),
const SizedBox(height: 20),
// 录音功能
_buildSectionCard(
title: AppStrings.recordFunction,
icon: Icons.mic,
children: [
_buildApiButton(
AppStrings.setRecordControl,
Icons.fiber_manual_record,
_startAudioRecord,
subtitle: AppStrings.setRecordControl,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.stopRecordControl,
Icons.stop,
_stopAudioRecord,
subtitle: AppStrings.stopRecordControl,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.getRecordStatus,
Icons.info,
_queryAudioRecordState,
subtitle: _audioRecordStatus,
enabled: _isConnected,
),
],
),
const SizedBox(height: 20),
// 用户信息和设置
_buildSectionCard(
title: AppStrings.userInfoSettings,
icon: Icons.person_outline,
children: [
_buildApiButton(
AppStrings.setUserInfo,
Icons.person,
_setUserInfo,
subtitle: AppStrings.setUserInfo,
enabled: _isConnected,
),
// 语言设置功能已移至设备基础功能模块,避免重复
],
),
const SizedBox(height: 20),
// 直播功能
_buildSectionCard(
title: AppStrings.liveFunction,
icon: Icons.live_tv,
children: [
_buildApiButton(
AppStrings.enterLiveMode,
Icons.videocam,
_enterLiveStream,
subtitle: AppStrings.enterLiveMode,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.exitLiveMode,
Icons.videocam_off,
_exitLiveStream,
subtitle: AppStrings.exitLiveMode,
enabled: _isConnected,
),
],
),
const SizedBox(height: 20),
// OTA 升级功能
_buildSectionCard(
title: AppStrings.otaUpgradeFunction,
icon: Icons.system_update,
children: [
// 版本信息子标题
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Icon(Icons.info_outline, size: 20, color: Colors.grey[600]),
const SizedBox(width: 8),
Text(
AppStrings.currentVersionInfo,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
],
),
),
_buildApiButton(
AppStrings.queryDeviceVersion,
Icons.info,
_queryDeviceVersion,
subtitle: _deviceVersion,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.onlyJlCancellable,
Icons.memory,
_getJLVersion,
subtitle: _jlVersion,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.queryAllwinnerVersion,
Icons.developer_board,
_getQZVersion,
subtitle: _qzVersion,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.queryGitHashVersion,
Icons.code,
_getGithashVersion,
subtitle: _githashVersion,
enabled: _isConnected,
),
// OTA状态显示
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.system_update, size: 20, color: Colors.orange[700]),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.otaUpgradeStatus,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
_otaStatus,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.orange[700],
),
),
],
),
),
],
),
),
// 分隔线
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Divider(height: 1, color: Colors.grey[300]),
),
// OTA升级子标题
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Icon(Icons.system_update_alt, size: 20, color: Colors.grey[600]),
const SizedBox(width: 8),
Text(
AppStrings.selectFirmware,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
],
),
),
_buildApiButton(
AppStrings.checkFirmwareUpdate,
Icons.system_update_alt,
_checkLatestVersion,
subtitle: AppStrings.checkForNewVersion,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.jlOtaUpgrade,
Icons.memory,
_startJLOTA,
subtitle: AppStrings.selectFirmwareFile,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.cancelJlOta,
Icons.cancel,
_cancelOTA,
subtitle: AppStrings.onlyJlCancellable,
enabled: _isConnected,
),
// 分隔线
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Divider(height: 1, color: Colors.grey[300]),
),
_buildApiButton(
AppStrings.qzOtaUpgrade,
Icons.developer_board,
_startQZOTA,
subtitle: AppStrings.wifiOtaNote,
enabled: _isConnected,
),
],
),
const SizedBox(height: 20),
// 文件管理功能
_buildSectionCard(
title: AppStrings.fileManagementFunction,
icon: Icons.folder,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.link, color: Colors.blue[600], size: 20),
const SizedBox(width: 8),
Text(
AppStrings.fileBaseUrl,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.blue[600],
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: Text(
_fileBaseUrl,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 13,
color: Colors.blue[700],
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
),
),
_buildApiButton(
AppStrings.queryFileCount,
Icons.numbers,
_queryFileCount,
subtitle: AppStrings.queryFileCount,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.queryFileSyncMethod,
Icons.sync_alt,
_queryFileSyncType,
subtitle: AppStrings.queryFileSyncMethod,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.deleteMediaFile,
Icons.delete,
_deleteMediaFile,
subtitle: AppStrings.deleteMediaFile,
enabled: _isConnected,
),
// SDK日志显示
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.bug_report, size: 20, color: Colors.grey[700]),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.sdkLatestLog,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
_sdkLogStatus,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
],
),
const SizedBox(height: 20),
// 设备管理功能
_buildSectionCard(
title: AppStrings.deviceManagementFunction,
icon: Icons.settings_applications,
children: [
_buildApiButton(
AppStrings.clearPairInfo,
Icons.link_off,
_clearPairInfo,
subtitle: AppStrings.clearPairInfo,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.getDeviceUUID,
Icons.fingerprint,
_getDeviceUUID,
subtitle: _deviceUUIDStatus,
enabled: _isConnected,
),
_buildApiButton(
AppStrings.getVoiceWakeupStatus,
Icons.voice_over_off,
_getVoiceWakeupState,
subtitle: _voiceWakeupStatus,
enabled: _isConnected,
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.setVoiceWakeup,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[800],
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.operationType,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
DropdownButton<int>(
value: _selectedVoiceWakeupAction,
isExpanded: true,
items: [
DropdownMenuItem(value: 0, child: Text(AppStrings.enableVoiceWakeupTypeOn)),
DropdownMenuItem(value: 1, child: Text(AppStrings.disableVoiceWakeupTypeOff)),
],
onChanged: _isConnected ? (value) {
setState(() {
_selectedVoiceWakeupAction = value!;
});
} : null,
),
],
),
),
],
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isConnected ? _setVoiceWakeup : null,
child: Text(_selectedVoiceWakeupAction == 0 ? AppStrings.enableVoiceWakeup : AppStrings.disableVoiceWakeup),
),
),
],
),
),
_buildApiButton(
AppStrings.getRunningStatus,
Icons.info_outline,
_getRunningStatus,
subtitle: _runningStatus,
enabled: _isConnected,
),
// 实时运行状态显示
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.play_circle_outline, size: 20, color: Colors.green[700]),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppStrings.deviceRunningStatus,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
_runningStatus == AppStrings.clickToGet ? AppStrings.clickToQueryOrAutoUpdate : _runningStatus,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.green[700],
),
),
],
),
),
],
),
),
],
),
const SizedBox(height: 20),
const SizedBox(height: 40),
],
),
),
),
),
);
}
// ==================== UI 构建方法 ====================
Widget _buildStatusCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.info_outline, color: Colors.blue[600]),
const SizedBox(width: 8),
Text(
AppStrings.deviceStatus,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 12),
_buildStatusRow(AppStrings.connectionState, _connectionStatus,
_isConnected ? Colors.green : Colors.red),
_buildStatusRow(AppStrings.batteryLevel, _batteryLevel, Colors.orange),
_buildStatusRow(AppStrings.deviceVersion, _deviceVersion, Colors.blue),
// 显示设备名称和 MAC 地址
if (_cachedDeviceName != null && _cachedMacAddress != null) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.devices, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
AppStrings.connectedDevice,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 4),
Text(
_cachedDeviceName!,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Text(
_cachedMacAddress!,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
],
],
),
),
);
}
Widget _buildStatusRow(String label, String value, Color color) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontSize: 14, color: Colors.grey)),
Expanded(
child: Text(
value,
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: color),
),
),
],
),
);
}
Widget _buildSectionCard({
required String title,
required IconData icon,
required List<Widget> children,
}) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: Colors.blue[600]),
const SizedBox(width: 8),
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 12),
...children,
],
),
),
);
}
Widget _buildApiButton(
String title,
IconData icon,
VoidCallback onPressed,
{
String? subtitle,
bool enabled = true,
}) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 4),
child: Material(
color: enabled ? Colors.white : Colors.grey[100],
borderRadius: BorderRadius.circular(8),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: enabled ? onPressed : null,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Icon(
icon,
color: enabled ? Colors.blue[600] : Colors.grey,
size: 24,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: enabled ? Colors.black87 : Colors.grey,
),
),
if (subtitle != null)
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: enabled ? Colors.grey[600] : Colors.grey,
),
),
],
),
),
Icon(
Icons.chevron_right,
color: enabled ? Colors.grey[400] : Colors.grey[300],
),
],
),
),
),
),
);
}
// ==================== API 调用方法 ====================
void requestPermissions() {
[
Permission.location,
Permission.storage,
Permission.manageExternalStorage,
Permission.bluetoothConnect,
Permission.bluetoothScan,
Permission.bluetoothAdvertise
].request().then((value) => {
setState(() {
Map<Permission, PermissionStatus> statuses = value;
if (statuses[Permission.location] == PermissionStatus.denied) {
_permissionTxt = AppStrings.locationPermissionDenied;
return;
}
if (statuses[Permission.storage] == PermissionStatus.denied) {
_permissionTxt = AppStrings.storagePermissionDenied;
return;
}
_permissionTxt = AppStrings.permissionGranted;
})
});
}
void _navigateToScanPage(BuildContext context) async {
if (!mounted) return;
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const GlassesScanPage(),
),
);
// 处理连接结果
if (result != null && result is Map<String, dynamic>) {
if (result['connected'] == true) {
final device = result['device'] as BleScanBean;
_showToast(AppStrings.connectedTo(device.name.isEmpty ? device.address : device.name));
// 连接成功后更新基本信息
// 注意:不要手动设置 _isConnected,让事件流来更新状态
_updateConnectionState();
}
}
}
/// 检查初始蓝牙状态
void _checkInitialBluetoothState() async {
try {
bool isEnabled = await _glassesPlugin.checkBluetoothEnable;
// 根据返回值设置初始状态
setState(() {
_bluetoothState = isEnabled ? AppStrings.bluetoothEnabled : AppStrings.bluetoothDisabled;
});
debugPrint('Initial Bluetooth state: $_bluetoothState');
} catch (e) {
debugPrint('Failed to check initial Bluetooth state: $e');
setState(() {
_bluetoothState = AppStrings.checkFailed;
});
}
}
// 标记是否已经完成初始连接检查
bool _hasCheckedInitialConnection = false;
/// 检查初始连接状态,解决时序问题
void _checkInitialConnectionState() async {
// 如果已经检查过且状态已同步,则跳过
if (_hasCheckedInitialConnection && _isConnected) {
debugPrint('Initial connection already checked and synced');
return;
}
try {
debugPrint('Checking initial connection state...');
// 检查是否有缓存的设备信息
if (_cachedMacAddress != null && _cachedMacAddress!.isNotEmpty) {
debugPrint('Found cached device, checking connection status...');
// 如果有缓存的设备信息,尝试查询电池来判断是否真的连接
try {
await _glassesPlugin.queryBattery();
debugPrint('Battery query successful, device is connected');
if (!_isConnected) {
setState(() {
_isConnected = true;
_connectionStatus = AppStrings.connected;
});
debugPrint('Updated UI state to connected');
_hasCheckedInitialConnection = true;
// 触发连接成功后的操作(静默,不显示Toast)
if (!_hasQueriedBattery) {
await _loadCachedMacAddress();
// 注意:这里不再重复调用queryBattery,因为刚才已经查询过了
// 直接使用刚才的查询结果更新UI
_queryDeviceVersion();
_hasQueriedBattery = true;
}
}
} catch (e) {
debugPrint('Battery query failed, device may not be connected: $e');
// 只有在第一次检查失败时才更新状态
if (!_hasCheckedInitialConnection) {
setState(() {
_isConnected = false;
_connectionStatus = AppStrings.disconnected;
});
}
}
} else {
debugPrint('No cached device found');
_hasCheckedInitialConnection = true;
}
} catch (e) {
debugPrint('Failed to check initial connection state: $e');
}
}
void _updateConnectionState() async {
// 更新 UI 状态显示,基于当前的连接状态
_showToast(_isConnected ? AppStrings.deviceConnected : AppStrings.deviceNotConnected);
// 如果已连接,加载缓存的设备信息并查询一些基本信息
if (_isConnected) {
// 重新加载缓存的设备信息(可能在扫描页面更新了)
await _loadCachedMacAddress();
_queryBattery();
_queryDeviceVersion();
}
}
void _reconnectDevice() async {
try {
debugPrint('Attempting to reconnect device...');
_showToast(AppStrings.reconnecting);
await _glassesPlugin.reconnect();
_showToast(AppStrings.reconnectCommandSent);
debugPrint('Reconnect command sent');
} catch (e) {
debugPrint('Reconnect failed: $e');
_showToast(AppStrings.reconnectFailed + ": $e");
}
}
void _removeDevice() async {
try {
await _glassesPlugin.disconnect();
setState(() {
_isConnected = false;
_batteryLevel = AppStrings.unknown;
_isCharging = false;
_deviceVersion = AppStrings.unknown;
_cachedMacAddress = null; // 清除缓存的 MAC 地址
_cachedDeviceName = null; // 清除缓存的设备名称
});
// 清除 MAC 地址缓存
await MacAddressCache.clearCachedMac();
_showToast(AppStrings.deviceRemovedAndDisconnected);
} catch (e) {
_showToast(AppStrings.removeDeviceFailed + ": $e");
}
}
void _syncTime() async {
try {
await _glassesPlugin.syncTime();
_showToast(AppStrings.timeSyncSuccess);
} catch (e) {
_showToast(AppStrings.timeSyncFailed + ": $e");
}
}
void _queryDeviceVersion() async {
try {
String version = await _glassesPlugin.queryDeviceVersion();
setState(() {
_actualDeviceVersion = version;
_deviceVersion = "${AppStrings.deviceVersion}: $version";
});
_showToast(AppStrings.versionQuerySuccess + ": $version");
} catch (e) {
_showToast(AppStrings.versionQueryFailed + ": $e");
}
}
void _queryBattery() async {
try {
Map<String, dynamic> batteryInfo = await _glassesPlugin.queryBattery();
debugPrint('Battery query returned data: $batteryInfo');
int batteryLevel = 0;
bool isCharging = false;
// 处理返回的数据
if (batteryInfo['battery'] != null) {
batteryLevel = int.tryParse(batteryInfo['battery'].toString()) ?? 0;
}
if (batteryInfo['charging'] != null) {
isCharging = batteryInfo['charging'].toString().toLowerCase() == 'true';
}
setState(() {
_actualBatteryLevel = batteryLevel;
_batteryLevel = AppStrings.batteryDisplay("$batteryLevel", isCharging);
_isCharging = isCharging;
});
_showToast(AppStrings.batteryLevelToast("${batteryLevel}%", isCharging));
// 手动查询后重置标志,允许下次连接时再次自动查询
_hasQueriedBattery = false;
} catch (e) {
debugPrint('Battery query exception: $e');
_showToast(AppStrings.batteryQueryFailed + ": $e");
}
}
void _restartDevice() async {
try {
bool success = await _glassesPlugin.restart();
_showToast(success ? AppStrings.deviceRestartSuccess : AppStrings.deviceRestartFailed);
} catch (e) {
_showToast(AppStrings.deviceRestartFailed + ": $e");
}
}
void _takePhoto() async {
try {
await _glassesPlugin.takePhoto();
_showToast(AppStrings.photoCommandSent);
} catch (e) {
_showToast(AppStrings.photoFailed + ": $e");
}
}
void _startVideo() async {
try {
await _glassesPlugin.startVideo(fps: 30, maxDuration: 10);
_showToast(AppStrings.videoStartSuccess);
} catch (e) {
_showToast(AppStrings.videoStartFailed + ": $e");
}
}
void _stopVideo() async {
try {
await _glassesPlugin.stopVideo();
_showToast(AppStrings.videoStopCommandSent);
} catch (e) {
_showToast(AppStrings.videoStopFailed + ": $e");
}
}
void _queryVideoConfig() async {
try {
Map<String, dynamic> config = await _glassesPlugin.queryVideoConfig();
debugPrint('Retrieved config: $config');
// 安全地获取参数值
final frameRate = config['frameRate'] ?? '未知';
final maxDuration = config['maxDuration'] ?? '未知';
String configStr = AppStrings.videoConfigParams(frameRate, maxDuration);
_showToast(AppStrings.videoParams(configStr));
} catch (e) {
debugPrint('Error getting video parameters: $e');
_showToast(AppStrings.getVideoParamsFailed + ": $e");
}
}
void _setVideoConfig() async {
try {
// 使用用户选择的录像参数
Map<String, dynamic> config = {
"frameRate": _selectedFrameRate,
"duration": _selectedMaxDuration, // 注意:iOS端使用 duration 而不是 maxDuration
};
await _glassesPlugin.sendVideoConfig(config);
String fpsText = _selectedFrameRate == 0 ? AppStrings.notSet : '$_selectedFrameRate fps';
_showToast(AppStrings.videoParamsSet(fpsText, _selectedMaxDuration));
} catch (e) {
_showToast(AppStrings.setVideoParamsFailed + ": $e");
}
}
void _sendLanguage() async {
try {
await _glassesPlugin.sendLanguage(1); // 1 = 中文
_showToast(AppStrings.languageSettingsSent);
} catch (e) {
_showToast(AppStrings.sendLanguageSettingsFailed + ": $e");
}
}
void _resetDevice() async {
try {
bool success = await _glassesPlugin.reset();
_showToast(success ? AppStrings.deviceResetSuccess : AppStrings.deviceResetFailed);
} catch (e) {
_showToast(AppStrings.alarmSetFailed + ": $e");
}
}
void _shutdownDevice() async {
try {
bool success = await _glassesPlugin.shutdown();
_showToast(success ? AppStrings.deviceShutdownSuccess : AppStrings.deviceShutdownFailed);
} catch (e) {
_showToast(AppStrings.deviceShutdownFailed2 + ": $e");
}
}
// ==================== 音频控制方法 ====================
/// 设置音频控制状态
/// 调用 SDK 的 setAudioCtrl 方法,传入用户选择的音频动作类型
void _startAudio() async {
try {
await _glassesPlugin.setAudioControl(actionType: _selectedAudioAction);
String actionText = '';
switch (_selectedAudioAction) {
case 1:
actionText = AppStrings.startAudio;
break;
case 2:
actionText = AppStrings.cancelAudio;
break;
case 3:
actionText = AppStrings.dnsStreamStart;
break;
case 4:
actionText = AppStrings.dnsStreamPause;
break;
case 5:
actionText = AppStrings.dnsStreamStop;
break;
case 6:
actionText = AppStrings.normalStreamStart;
break;
case 7:
actionText = AppStrings.normalStreamPause;
break;
case 8:
actionText = AppStrings.normalStreamStop;
break;
}
_showToast(AppStrings.audioControlSet(actionText));
} catch (e) {
_showToast(AppStrings.setAudioControlFailed + ": $e");
}
}
/// 停止音频
/// 调用 SDK 的 setAudioCtrl 方法,传入 audioStop
void _stopAudio() async {
try {
await _glassesPlugin.setAudioControl(actionType: 0); // 0 = audioStop
_showToast(AppStrings.audioStopped);
} catch (e) {
_showToast(AppStrings.stopAudioFailed + ": $e");
}
}
/// 查询音频状态
/// 调用 SDK 的 queryAudioState 方法
void _queryAudioState() async {
try {
int state = await _glassesPlugin.queryAudioState();
String stateText = state == 1 ? AppStrings.inIntercom : AppStrings.notIntercom;
_showToast(AppStrings.audioStatus(stateText));
} catch (e) {
_showToast(AppStrings.queryAudioStateFailed + ": $e");
}
}
// ==================== AI 功能方法 ====================
/// 设置 AI 回复状态
/// 调用 SDK 的 setAIReplyStatus 方法
void _setAIReplyStatus() async {
try {
await _glassesPlugin.setAIReplyStatus(status: _selectedAIStatus);
String statusText = '';
switch (_selectedAIStatus) {
case 0:
statusText = AppStrings.startAiReply;
break;
case 1:
statusText = AppStrings.completeAiReply;
break;
case 2:
statusText = AppStrings.interruptAiReply;
break;
}
_showToast(AppStrings.aiReplyStatusSet(statusText));
} catch (e) {
_showToast(AppStrings.setAiReplyStatusFailed + ": $e");
}
}
/// 退出语音
void _exitAIReply() async {
try {
await _glassesPlugin.exitAIReply();
_showToast(AppStrings.exitedVoice);
} catch (e) {
_showToast(AppStrings.exitVoiceFailed + ": $e");
}
}
// ==================== 文件管理功能方法 ====================
/// 查询文件数量
/// 调用 SDK 的 getFileCount 方法
void _queryFileCount() async {
try {
int count = await _glassesPlugin.getFileCount();
_showToast(AppStrings.deviceFileCount(count));
} catch (e) {
_showToast(AppStrings.queryFileCountFailed + ": $e");
}
}
/// 查询文件同步方式
/// 调用 SDK 的 getFileSyncType 方法
void _queryFileSyncType() async {
try {
int type = await _glassesPlugin.getFileSyncType();
String typeStr = type == 0 ? AppStrings.httpGetMode : AppStrings.otherMode;
_showToast(AppStrings.currentFileSyncMethod(typeStr));
} catch (e) {
_showToast(AppStrings.queryFileSyncMethodFailed + ": $e");
}
}
/// 删除文件
/// 调用 SDK 的 deleteFile 方法
void _deleteMediaFile() async {
try {
// 示例:删除一个图片文件
// 实际使用时应该从文件列表中选择
bool success = await _glassesPlugin.deleteFile(
fileType: 1, // 0-删除所有文件, 1-按名称删除, 2-按类型删除
fileName: "example.jpg",
);
if (success) {
_showToast(AppStrings.deleteFileSuccess);
} else {
_showToast(AppStrings.deleteFileFailed);
}
} catch (e) {
_showToast(AppStrings.deleteMediaFileFailed + ": $e");
}
}
/// 进入文件同步模式(开启 Wi-Fi)
/// 调用 SDK 的 setFileSyncModeEnter 方法
void _setFileSyncModeEnter() async {
try {
// 先检查电池电量,低电量时无法进入文件同步模式
if (_batteryLevel != AppStrings.unknown) {
// 从电池字符串中提取电量值(格式如 "50" 或 "50%")
int batteryValue = int.tryParse(_batteryLevel.replaceAll(RegExp(r'[^0-9]'), '')) ?? 0;
if (batteryValue < 20) {
_showToast(AppStrings.lowBatteryWarning(_batteryLevel));
return;
}
}
await _glassesPlugin.enableWifi(wifiType: 0);
_showToast(AppStrings.enteredFileSyncModeWifiOn);
} catch (e) {
_showToast(AppStrings.enterFileSyncModeFailed + ": $e");
}
}
/// 退出文件同步模式(关闭 Wi-Fi)
/// 调用 SDK 的 setFileSyncModeExit 方法
void _setFileSyncModeExit() async {
try {
await _glassesPlugin.disableWifi();
_showToast(AppStrings.exitedFileSyncModeWifiOff);
} catch (e) {
_showToast(AppStrings.exitFileSyncModeFailed + ": $e");
}
}
// ==================== 音频录音功能方法 ====================
/// 设置录音控制(开始录音)
/// 调用 SDK 的 setAudioRecord 方法,传入 Start
void _startAudioRecord() async {
try {
bool success = await _glassesPlugin.setAudioRecord(type: 0, totalTime: 300); // 默认录音5分钟
if (success) {
_showToast(AppStrings.recordingStarted);
} else {
_showToast(AppStrings.startRecordingFailed2);
}
} catch (e) {
_showToast(AppStrings.startRecordingFailed + ": $e");
}
}
/// 设置录音控制(停止录音)
/// 调用 SDK 的 setAudioRecord 方法,传入 Stop
void _stopAudioRecord() async {
try {
bool success = await _glassesPlugin.setAudioRecord(type: 1, totalTime: 0);
if (success) {
_showToast(AppStrings.recordingStopped);
} else {
_showToast(AppStrings.stopRecordingFailed2);
}
} catch (e) {
_showToast(AppStrings.stopRecordingFailed + ": $e");
}
}
/// 获取录音状态
/// 调用 SDK 的 getAudioRecordState 方法
void _queryAudioRecordState() async {
// 设置初始状态为获取中
setState(() {
_audioRecordStatus = AppStrings.gettingStatusWithDots;
});
try {
// 使用 timeout 设置10秒超时
Map<String, int> state = await _glassesPlugin.getAudioRecordState()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
int type = state['type'] ?? 0;
int totalTime = state['totalTime'] ?? 0;
String stateText = type == 0 ? AppStrings.notRecording : AppStrings.recording;
setState(() {
_audioRecordStatus = "$stateText, ${AppStrings.duration}: ${totalTime}${AppStrings.seconds}";
});
_showToast(AppStrings.recordingStatus(stateText, totalTime));
} on TimeoutException catch (e) {
setState(() {
_audioRecordStatus = AppStrings.sdkNotReturned;
});
_showToast(AppStrings.queryRecordStatusTimeout + ": $e");
} catch (e) {
// 显示SDK返回的错误信息
String errorMsg = e.toString();
// 检查多种错误模式
if (errorMsg.contains(AppStrings.undefinedCommand) ||
errorMsg.contains("Unknown") ||
errorMsg.contains("CRPACKError") ||
errorMsg.contains("rawValue: 1")) {
setState(() {
_audioRecordStatus = AppStrings.deviceNotSupported;
});
} else {
setState(() {
_audioRecordStatus = AppStrings.getFailed;
});
}
_showToast(AppStrings.getRecordStatusFailed + ": $e");
}
}
// ==================== 用户信息和设置方法 ====================
/// 设置用户信息
/// 调用 SDK 的 setUserInfo 方法
void _setUserInfo() async {
try {
// 示例用户信息
Map<String, dynamic> userInfo = {
'gender': false, // 性别:false-男,true-女
'age': 25, // 年龄
'height': 175, // 身高(cm)
'weight': 70, // 体重(kg)
};
bool success = await _glassesPlugin.setUserInfo(userInfo);
if (success) {
_showToast(AppStrings.userInfoSetSuccess);
} else {
_showToast(AppStrings.userInfoSetFailed);
}
} catch (e) {
_showToast(AppStrings.setUserInfoFailed + ": $e");
}
}
/// 设置闹钟
/// 调用 SDK 的 setAlarm 方法
void _setAlarm() async {
try {
// 示例闹钟设置:早上7:30,重复
Map<String, dynamic> alarmInfo = {
'enable': true, // 启用闹钟
'hour': 7, // 小时
'minute': 30, // 分钟
'repeat': 1, // 重复:0-单次,1-每天
};
bool success = await _glassesPlugin.setAlarm(alarmInfo);
if (success) {
_showToast(AppStrings.alarmSetSuccess);
} else {
_showToast(AppStrings.alarmSetFailed);
}
} catch (e) {
_showToast(AppStrings.setAlarmFailed + ": $e");
}
}
/// 获取语言设置
/// 调用 SDK 的 getLanguage 方法
void _getLanguage() async {
// 设置初始状态为获取中
setState(() {
_actualLanguageStatus = AppStrings.gettingStatusWithDots;
_languageStatus = AppStrings.gettingStatusWithDots;
});
try {
// 使用 timeout 设置10秒超时
String language = await _glassesPlugin.getLanguage()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
setState(() {
_actualLanguageStatus = language;
_languageStatus = language;
});
_showToast(AppStrings.currentLanguage(language));
} on TimeoutException catch (e) {
setState(() {
_actualLanguageStatus = AppStrings.sdkNotReturned;
_languageStatus = AppStrings.sdkNotReturned;
});
_showToast(AppStrings.getLanguageTimeout + ": $e");
} catch (e) {
// 显示SDK返回的错误信息
String errorMsg = e.toString();
if (errorMsg.contains('device not support')) {
setState(() {
_actualLanguageStatus = AppStrings.deviceNotSupportedFeature;
_languageStatus = AppStrings.deviceNotSupportedFeature;
});
_showToast(AppStrings.getFailed + ": $e");
} else {
setState(() {
_actualLanguageStatus = AppStrings.getFailed;
_languageStatus = AppStrings.getFailed;
});
_showToast(AppStrings.getLanguageFailed + ": $e");
}
}
}
// ==================== 直播功能方法 ====================
/// 进入直播模式
/// 调用 SDK 的 setLiveStreamEnter 方法
void _enterLiveStream() async {
try {
// 默认使用 AP 模式(0)
bool success = await _glassesPlugin.enterLiveStream(wifiType: 0);
if (success) {
_showToast(AppStrings.enteredLiveModeAp);
} else {
_showToast(AppStrings.enterLiveModeFailed2);
}
} catch (e) {
_showToast(AppStrings.enterLiveModeFailed + ": $e");
}
}
/// 退出直播模式
/// 调用 SDK 的 setLiveStreamExit 方法
void _exitLiveStream() async {
try {
bool success = await _glassesPlugin.exitLiveStream();
if (success) {
_showToast(AppStrings.exitedLiveMode);
} else {
_showToast(AppStrings.exitLiveModeFailed2);
}
} catch (e) {
_showToast(AppStrings.exitLiveModeFailed + ": $e");
}
}
// ==================== 设备管理功能方法 ====================
/// 清除配对信息
/// 调用 SDK 的 clearPairInfo 方法
void _clearPairInfo() async {
try {
bool success = await _glassesPlugin.clearPairInfo();
if (success) {
// 清除 MAC 地址缓存
setState(() {
_cachedMacAddress = null;
_cachedDeviceName = null;
});
await MacAddressCache.clearCachedMac();
_showToast(AppStrings.clearedPairInfo);
} else {
_showToast(AppStrings.clearPairInfoFailed2);
}
} catch (e) {
_showToast(AppStrings.clearPairInfoFailed + ": $e");
}
}
/// 获取设备UUID
/// 调用 SDK 的 getDeviceUUID 方法
void _getDeviceUUID() async {
// 设置初始状态为获取中
setState(() {
_actualDeviceUUIDStatus = AppStrings.gettingStatus;
_deviceUUIDStatus = AppStrings.gettingStatus;
});
try {
// 使用 timeout 设置10秒超时
String uuid = await _glassesPlugin.getDeviceUUID()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
setState(() {
_actualDeviceUUIDStatus = uuid;
_deviceUUIDStatus = uuid;
});
_showToast(AppStrings.deviceUuid(uuid));
} on TimeoutException catch (e) {
setState(() {
_actualDeviceUUIDStatus = AppStrings.sdkNotReturned;
_deviceUUIDStatus = AppStrings.sdkNotReturned;
});
_showToast(AppStrings.getDeviceUuidTimeout + ": $e");
} catch (e) {
// 显示SDK返回的错误信息
String errorMsg = e.toString();
if (errorMsg.contains('device not support')) {
setState(() {
_actualDeviceUUIDStatus = AppStrings.deviceNotSupported;
_deviceUUIDStatus = AppStrings.deviceNotSupported;
});
_showToast(AppStrings.getFailed + ": $e");
} else {
setState(() {
_actualDeviceUUIDStatus = AppStrings.getFailed;
_deviceUUIDStatus = AppStrings.getFailed;
});
_showToast(AppStrings.getDeviceUuidFailed + ": $e");
}
}
}
/// 获取语音唤醒状态
/// 调用 SDK 的 readVoiceWakeupState 方法
void _getVoiceWakeupState() async {
// 设置初始状态为获取中
setState(() {
_actualVoiceWakeupStatus = AppStrings.gettingStatusWithDots;
_voiceWakeupStatus = AppStrings.gettingStatusWithDots;
});
try {
// 使用 timeout 设置10秒超时
bool isEnabled = await _glassesPlugin.getVoiceWakeupState()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
String statusText = isEnabled ? AppStrings.enabled : AppStrings.disabled;
setState(() {
_actualVoiceWakeupStatus = statusText;
_voiceWakeupStatus = statusText;
});
_showToast(AppStrings.voiceWakeupStatus(isEnabled ? AppStrings.enabled : AppStrings.disabled));
} catch (e) {
debugPrint('Voice wakeup state exception: $e');
// 显示SDK返回的错误信息
String errorMsg = e.toString();
// 检查多种错误模式
if (errorMsg.contains(AppStrings.undefinedCommand) ||
errorMsg.contains("Unknown") ||
errorMsg.contains("CRPACKError")) {
setState(() {
_voiceWakeupStatus = AppStrings.deviceNotSupportedFeature;
});
} else {
setState(() {
_voiceWakeupStatus = AppStrings.notSupportedOrError;
});
}
_showToast(AppStrings.voiceWakeupMayNotSupport + ": $e");
}
}
/// 设置语音唤醒
/// 调用 SDK 的 setVoiceWakeup 方法
void _setVoiceWakeup() async {
try {
// SDK 定义:0=TypeOn(开启),1=TypeOff(关闭)
bool enable = _selectedVoiceWakeupAction == 0;
await _glassesPlugin.setVoiceWakeup(enable: enable);
_showToast(AppStrings.voiceWakeupSet(enable ? AppStrings.enabled : AppStrings.disabled));
// 设置完成后重新获取状态
_getVoiceWakeupState();
} catch (e) {
_showToast(AppStrings.setVoiceWakeupFailed + ": $e");
}
}
/// 获取运行状态
/// 调用 SDK 的 readRunningStatus 方法
void _getRunningStatus() async {
// 设置初始状态为获取中
setState(() {
_actualRunningStatus = AppStrings.gettingStatusWithDots;
_runningStatus = AppStrings.gettingStatusWithDots;
});
try {
// 使用 timeout 设置10秒超时
String status = await _glassesPlugin.getRunningStatus()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
setState(() {
_actualRunningStatus = status;
_runningStatus = status;
});
_showToast(AppStrings.runningStatus(status));
} catch (e) {
debugPrint('Running status exception: $e');
setState(() {
_actualRunningStatus = AppStrings.deviceNotSupportedFeature2;
_runningStatus = AppStrings.deviceNotSupportedFeature2;
});
_showToast(AppStrings.getFailed + ": $e");
}
}
/// 获取杰里版本号
Future<void> _getJLVersion() async {
setState(() {
_jlVersion = AppStrings.gettingStatusWithDots;
});
try {
String version = await _glassesPlugin.getJLVersion()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
setState(() {
_actualJlVersion = version;
_jlVersion = AppStrings.jlVersion(version);
});
_showToast(AppStrings.jlVersion(version));
} on TimeoutException catch (e) {
setState(() {
_jlVersion = AppStrings.sdkNotReturned;
});
_showToast(AppStrings.getJlVersionTimeout + ": $e");
} catch (e) {
setState(() {
_jlVersion = AppStrings.getFailed;
});
_showToast(AppStrings.getJlVersionFailed + ": $e");
}
}
/// 获取全志版本号
Future<void> _getQZVersion() async {
setState(() {
_qzVersion = AppStrings.gettingStatusWithDots;
});
try {
String version = await _glassesPlugin.getQZVersion()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
setState(() {
_actualQzVersion = version;
_qzVersion = AppStrings.qzVersion(version);
});
_showToast(AppStrings.qzVersion(version));
} on TimeoutException catch (e) {
setState(() {
_qzVersion = AppStrings.sdkNotReturned;
});
_showToast(AppStrings.getQzVersionTimeout + ": $e");
} catch (e) {
setState(() {
_qzVersion = AppStrings.getFailed;
});
_showToast(AppStrings.getQzVersionFailed + ": $e");
}
}
/// 获取Git哈希版本号
Future<void> _getGithashVersion() async {
setState(() {
_githashVersion = AppStrings.gettingStatusWithDots;
});
try {
String version = await _glassesPlugin.getGithashVersion()
.timeout(const Duration(seconds: 10), onTimeout: () {
throw TimeoutException(AppStrings.sdkTimeoutMessage, const Duration(seconds: 10));
});
setState(() {
_githashVersion = version;
});
_showToast(AppStrings.githashVersion(version));
} on TimeoutException catch (e) {
setState(() {
_githashVersion = AppStrings.sdkNotReturned;
});
_showToast(AppStrings.getGithashVersionTimeout + ": $e");
} catch (e) {
setState(() {
_githashVersion = AppStrings.getFailed;
});
_showToast(AppStrings.getGithashVersionFailed + ": $e");
}
}
void _showToast(String message) {
ToastUtil.showToast(message);
}
// ==================== OTA 升级功能方法 ====================
/// 检查最新版本
void _checkLatestVersion() async {
// _showToast(AppStrings.gettingVersionInfo);
try {
// 先获取当前版本信息
await _getJLVersion();
await _getQZVersion();
// 检查杰里版本
if (_jlVersion == AppStrings.unknown || _jlVersion.isEmpty) {
_showToast(AppStrings.jlVersionMissing);
return;
}
// 检查全志版本
if (_qzVersion == AppStrings.unknown || _qzVersion.isEmpty) {
_showToast(AppStrings.qzVersionMissing);
return;
}
// 检查MAC地址
String mac = _cachedMacAddress ?? "00:00:00:00:00:00";
if (mac == "00:00:00:00:00:00" || mac.isEmpty) {
_showToast(AppStrings.connectDeviceForMac);
return;
}
// _showToast(AppStrings.checkingLatestVersion);
Map<String, dynamic> result = await _glassesPlugin.checkLatestVersion(
fw1Ver: _jlVersion,
fw2Ver: _qzVersion,
mac: mac,
);
// 处理结构化的返回结果
String status = result['status'] ?? 'unknown';
String messageKey = result['message'] ?? 'unknown';
bool hasUpdate = result['hasUpdate'] ?? false;
// 根据消息键获取国际化文本
String localizedMessage = _getLocalizedMessage(messageKey);
_showToast(localizedMessage);
// 如果有更新,显示更多详细信息
if (hasUpdate && result['latestVersion'] != null) {
Map<String, dynamic> latestVersion = result['latestVersion'];
String firmwareVer = latestVersion['firmwareVer'] ?? '';
String firmwareFile = latestVersion['firmwareFile'] ?? '';
int firmwareSize = latestVersion['firmwareSize'] ?? 0;
String firmwareMd5 = latestVersion['firmwareMd5'] ?? '';
debugPrint('发现新版本: $firmwareVer');
debugPrint('固件文件: $firmwareFile');
debugPrint('固件大小: ${(firmwareSize / 1024 / 1024).toStringAsFixed(2)} MB');
debugPrint('MD5校验: $firmwareMd5');
// 可以在这里添加更新提示对话框
_showUpdateDialog(latestVersion);
} else if (status == 'error' && result['error'] != null) {
Map<String, dynamic> error = result['error'];
debugPrint('检查版本错误: ${error['message']}');
}
} catch (e) {
_showToast(AppStrings.checkVersionFailed + ": $e");
}
}
/// 根据消息键获取国际化文本
String _getLocalizedMessage(String messageKey) {
switch (messageKey) {
case 'update_available':
return AppStrings.updateAvailable;
case 'already_latest':
return AppStrings.alreadyLatest;
case 'check_failed':
return AppStrings.checkFailed;
default:
return AppStrings.checkVersionFailed; // 默认错误消息
}
}
/// 显示更新提示对话框
void _showUpdateDialog(Map<String, dynamic> latestVersion) {
String firmwareVer = latestVersion['firmwareVer'] ?? '';
String firmwareFile = latestVersion['firmwareFile'] ?? '';
int type = latestVersion['type'] ?? 0;
int firmwareNum = latestVersion['firmwareNum'] ?? 0;
String firmwareMd5 = latestVersion['firmwareMd5'] ?? '';
int firmwareSize = latestVersion['firmwareSize'] ?? 0;
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('发现新版本 $firmwareVer'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('固件版本: $firmwareVer', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8),
if (firmwareFile.isNotEmpty) ...[
Text('固件文件: $firmwareFile'),
SizedBox(height: 8),
],
Text('固件类型: $type'),
SizedBox(height: 8),
Text('固件编号: $firmwareNum'),
SizedBox(height: 8),
if (firmwareSize > 0) ...[
Text('固件大小: ${(firmwareSize / 1024 / 1024).toStringAsFixed(2)} MB'),
SizedBox(height: 8),
],
if (firmwareMd5.isNotEmpty) ...[
Text('MD5校验:', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 4),
SelectableText(firmwareMd5, style: TextStyle(fontSize: 12, color: Colors.blue)),
],
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('稍后更新'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
// TODO: 启动OTA升级流程
_showToast('OTA升级功能开发中...');
},
child: Text('立即更新'),
),
],
);
},
);
}
/// 启动杰里OTA升级
void _startJLOTA() async {
_showToast(AppStrings.selectFirmwareFile);
// TODO: 实现文件选择功能
// 这里暂时使用模拟路径
String firmwarePath = "/path/to/firmware.bin";
try {
bool success = await _glassesPlugin.startJLOTA(path: firmwarePath);
if (success) {
_showToast(AppStrings.jlOtaStarted);
// TODO: 显示升级进度
} else {
_showToast(AppStrings.jlOtaStartFailed);
}
} catch (e) {
_showToast("${AppStrings.jlOtaStartFailed}: $e");
}
}
/// 启动全志OTA升级
void _startQZOTA() async {
_showToast(AppStrings.qzOtaNotImplemented);
}
/// 取消杰里OTA升级(仅适用于杰里芯片)
/// 注意:全志OTA升级无法取消
void _cancelOTA() async {
try {
_showToast(AppStrings.cancellingOta);
bool success = await _glassesPlugin.cancelJLOTA();
if (success) {
_showToast(AppStrings.otaCancelled);
} else {
_showToast(AppStrings.cancelOtaFailed);
}
} catch (e) {
_showToast("${AppStrings.cancelOtaFailed}: $e");
}
}
}